diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 35500edd3b..e398907d3b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,3 +32,11 @@ updates: commit-message: prefix: chore include: scope +- package-ecosystem: "docker" + directory: "/site/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore + include: scope \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ad85adab0d..004a2c1e1b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,15 +17,19 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v2 - - name: Install and Build - run: | - npm install - npx sapper export --legacy + - uses: docker/build-push-action@v2 + with: + context: . + file: ./site/dockerfile + platforms: linux/amd64 + tags: zitadel:docs + push: false + outputs: type=local,dest=output - name: Archive Production Artifact uses: actions/upload-artifact@master with: name: export - path: site/__sapper__/export + path: output deploydocs: name: Deploy needs: builddocs diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml deleted file mode 100644 index a2590ac451..0000000000 --- a/.github/workflows/spellcheck.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Spellcheck - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - spellcheck: - name: Typo CI (GitHub Action) - runs-on: ubuntu-latest - timeout-minutes: 4 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - steps: - - name: TypoCheck - uses: typoci/spellcheck-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.typo-ci.yml b/.typo-ci.yml deleted file mode 100644 index 0f9c20faf8..0000000000 --- a/.typo-ci.yml +++ /dev/null @@ -1,42 +0,0 @@ -# What language dictionaries should it use? Currently Typo CI supports: -# de -# en -# en_GB -# es -# fr -# it -# pt -# pt_BR -dictionaries: - - en - - en_GB - - de - -# Any files/folders we should ignore? -excluded_files: - - ".codecov/*" - - ".github/*" - - "build/*" - - "k8s/*" - - "*.min.css" - - "*.css.map" - - "*.min.js" - - "*.js.map" - - "package-lock.json" - - "package.json" - - ".releaserc.js" - - ".typo-ci.yml" - - ".gitignore" - - "go.mod" - - "go.sum" - -# Any typos we should ignore? -excluded_words: - - typoci - - idps - - ZITADEL's - - otel - - otlp - -# Would you like filenames to also be spellchecked? -spellcheck_filenames: false \ No newline at end of file diff --git a/README.md b/README.md index d3dcf9395d..7f7d69c73e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Go check it out under [zitadel.ch](https://zitadel.ch) ### Run your own IAM Stay tuned, we will soon publish a guide how you can deploy a **hyperconverged** system with our automation tooling called [**ORBOS**](https://github.com/caos/orbos/). -With [**ORBOS**](https://github.com/caos/orbos/) you will be able to run [**Kubernetes**](https://kubernetes.io/) on **GCE** or **StaticProvider** within 20 minutes. To achieve this, [[**ORBOS**](https://github.com/caos/orbos/) will bootstrap and maintain a [**Kubernetes**](https://kubernetes.io/) cluster, essential platform components (logging, metrics, ingress, ...), a secure [**CockroachDB**](https://www.cockroachlabs.com/) cluster and **ZITADEL** itself. +With [**ORBOS**](https://github.com/caos/orbos/) you will be able to run [**Kubernetes**](https://kubernetes.io/) with Google on **[GCEProvider](https://cloud.google.com/compute)**, on **[StaticProvider](https://github.com/caos/orbos/blob/master/docs/orbiter/static.md)** for in-house scenarios or on the Swiss based **[CloudscaleProvider](https://www.cloudscale.ch/)** within 20 minutes. To achieve this, [**ORBOS**](https://github.com/caos/orbos/) will bootstrap and maintain a [**Kubernetes**](https://kubernetes.io/) cluster, essential platform components (logging, metrics, ingress, ...), a secure [**CockroachDB**](https://www.cockroachlabs.com/) cluster and **ZITADEL** itself. The combination of the tools [**ORBOS**](https://github.com/caos/orbos/) and **ZITADEL** is what makes the operation easy and scalable. diff --git a/.dockerignore b/build/.dockerignore similarity index 100% rename from .dockerignore rename to build/.dockerignore diff --git a/build/dockerfile b/build/dockerfile index ca583d221a..5128278580 100644 --- a/build/dockerfile +++ b/build/dockerfile @@ -34,7 +34,7 @@ COPY internal/protoc/protoc-gen-authoption/authoption/options.proto authoption/o ## With this step we prepare all node_modules, this helps caching the build ## Speed up this step by mounting your local node_modules directory ####################### -FROM node:12 as npm-base +FROM node:15 as npm-base WORKDIR console COPY console/package.json console/package-lock.json ./ RUN npm install \ diff --git a/cmd/zitadel/caos_local.sh b/cmd/zitadel/caos_local.sh index 76ec5ae792..817761d14d 100755 --- a/cmd/zitadel/caos_local.sh +++ b/cmd/zitadel/caos_local.sh @@ -11,6 +11,8 @@ export ZITADEL_TRACING_FRACTION=0.1 export ZITADEL_TRACING_ENDPOINT=localhost:9096 export ZITADEL_TRACING_TYPE=google +export ZITADEL_METRICS_TYPE=otel + # Log export ZITADEL_LOG_LEVEL=debug diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 776fd3bb5b..c1004a077f 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + metrics "github.com/caos/zitadel/internal/telemetry/metrics/config" "github.com/caos/logging" admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing" @@ -21,7 +22,7 @@ import ( mgmt_es "github.com/caos/zitadel/internal/management/repository/eventsourcing" "github.com/caos/zitadel/internal/notification" "github.com/caos/zitadel/internal/setup" - tracing "github.com/caos/zitadel/internal/tracing/config" + tracing "github.com/caos/zitadel/internal/telemetry/tracing/config" "github.com/caos/zitadel/internal/ui" "github.com/caos/zitadel/internal/ui/console" "github.com/caos/zitadel/internal/ui/login" @@ -30,6 +31,7 @@ import ( type Config struct { Log logging.Config Tracing tracing.TracingConfig + Metrics metrics.MetricsConfig InternalAuthZ internal_authz.Config SystemDefaults sd.SystemDefaults @@ -125,14 +127,16 @@ func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) } func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsRepository, authRepo *auth_es.EsRepository) { - apis := api.Create(conf.API, conf.InternalAuthZ, authZRepo, conf.SystemDefaults) roles := make([]string, len(conf.InternalAuthZ.RolePermissionMappings)) for i, role := range conf.InternalAuthZ.RolePermissionMappings { roles[i] = role.Role } + adminRepo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, roles) + logging.Log("API-D42tq").OnError(err).Fatal("error starting auth repo") + + apis := api.Create(conf.API, conf.InternalAuthZ, authZRepo, authRepo, adminRepo, conf.SystemDefaults) + if *adminEnabled { - adminRepo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, roles) - logging.Log("API-D42tq").OnError(err).Fatal("error starting auth repo") apis.RegisterServer(ctx, admin.CreateServer(adminRepo)) } if *managementEnabled { diff --git a/cmd/zitadel/setup.yaml b/cmd/zitadel/setup.yaml index 3272ca011e..79611a5022 100644 --- a/cmd/zitadel/setup.yaml +++ b/cmd/zitadel/setup.yaml @@ -96,4 +96,6 @@ SetUp: PrimaryColor: '#222324' SecondaryColor: '#ffffff' Step7: - DefaultSecondFactor: 1 #SecondFactorTypeOTP \ No newline at end of file + DefaultSecondFactor: 1 #SecondFactorTypeOTP + Step8: + DefaultSecondFactor: 2 #SecondFactorTypeU2F \ No newline at end of file diff --git a/cmd/zitadel/startup.yaml b/cmd/zitadel/startup.yaml index 1d3fff247f..88f5a5e1c9 100644 --- a/cmd/zitadel/startup.yaml +++ b/cmd/zitadel/startup.yaml @@ -11,6 +11,11 @@ Tracing: Fraction: $ZITADEL_TRACING_FRACTION Endpoint: $ZITADEL_TRACING_ENDPOINT +Metrics: + Type: 'otel' + Config: + MeterName: 'github.com/caos/zitadel' + AuthZ: Repository: Eventstore: @@ -224,7 +229,7 @@ UI: Login: Handler: BaseURL: '$ZITADEL_ACCOUNTS' - OidcAuthCallbackURL: '$ZITADEL_AUTHORIZE/authorize/' + OidcAuthCallbackURL: '$ZITADEL_AUTHORIZE/authorize/callback?id=' ZitadelURL: '$ZITADEL_CONSOLE' LanguageCookieName: 'caos.zitadel.login.lang' DefaultLanguage: 'de' diff --git a/cmd/zitadel/system-defaults.yaml b/cmd/zitadel/system-defaults.yaml index f4cf9c2be2..5faee8baec 100644 --- a/cmd/zitadel/system-defaults.yaml +++ b/cmd/zitadel/system-defaults.yaml @@ -53,7 +53,7 @@ SystemDefaults: VerificationLifetimes: PasswordCheck: 240h #10d ExternalLoginCheck: 240h #10d - MfaInitSkip: 720h #30d + MFAInitSkip: 720h #30d SecondFactorCheck: 18h MultiFactorCheck: 12h IamID: 'IAM' @@ -124,4 +124,8 @@ SystemDefaults: Subject: 'DomainClaimed.Subject' Greeting: 'DomainClaimed.Greeting' Text: 'DomainClaimed.Text' - ButtonText: 'DomainClaimed.ButtonText' \ No newline at end of file + ButtonText: 'DomainClaimed.ButtonText' + WebAuthN: + ID: $ZITADEL_COOKIE_DOMAIN + Origin: $ZITADEL_ACCOUNTS + DisplayName: ZITADEL \ No newline at end of file diff --git a/console/src/app/pages/iam/iam-views/iam-views.component.html b/console/src/app/pages/iam/iam-views/iam-views.component.html index 074e7be296..ce36d3b615 100644 --- a/console/src/app/pages/iam/iam-views/iam-views.component.html +++ b/console/src/app/pages/iam/iam-views/iam-views.component.html @@ -18,13 +18,20 @@ {{view.processedSequence}} - - {{ 'IAM.VIEWS.TIMESTAMP' | translate }} + + {{ 'IAM.VIEWS.EVENTTIMESTAMP' | translate }} - {{view?.viewTimestamp | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} + {{view?.eventTimestamp | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} + + {{ 'IAM.VIEWS.LASTSPOOL' | translate }} + + {{view?.lastSuccessfulSpoolerRun | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} + + + @@ -41,4 +48,4 @@ - \ No newline at end of file + diff --git a/console/src/app/pages/iam/iam-views/iam-views.component.ts b/console/src/app/pages/iam/iam-views/iam-views.component.ts index 343649a372..c1f07a3fb5 100644 --- a/console/src/app/pages/iam/iam-views/iam-views.component.ts +++ b/console/src/app/pages/iam/iam-views/iam-views.component.ts @@ -21,7 +21,7 @@ export class IamViewsComponent implements AfterViewInit { @ViewChild(MatPaginator) public paginator!: MatPaginator; public dataSource!: MatTableDataSource; - public displayedColumns: string[] = ['viewName', 'database', 'sequence', 'timestamp', 'actions']; + public displayedColumns: string[] = ['viewName', 'database', 'sequence', 'eventTimestamp', 'lastSuccessfulSpoolerRun', 'actions']; private loadingSubject: BehaviorSubject = new BehaviorSubject(false); public loading$: Observable = this.loadingSubject.asObservable(); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 4feeac9ab1..a003ec328b 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -164,8 +164,8 @@ "OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.", "TYPE": { "0":"Keine MFA definiert", - "1":"SMS", - "2":"OTP" + "1":"OTP", + "2":"U2F" }, "STATE": { "0": "Kein Status", @@ -392,7 +392,8 @@ "VIEWNAME":"Name", "DATABASE":"Datenbank", "SEQUENCE":"Sequenz", - "TIMESTAMP":"Zeitstempel", + "EVENTTIMESTAMP":"Event Zeitstempel", + "LASTSPOOL": "Erfolgreicher Durchlauf", "ACTIONS":"Aktionen", "CLEAR":"Aufräumen", "CLEARED":"View wurde erfolgreich zurückgesetzt!", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 9fa4a9e2cc..b4f01ba663 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -164,8 +164,8 @@ "OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.", "TYPE": { "0": "No MFA defined", - "1": "SMS", - "2": "OTP" + "1": "OTP", + "2": "U2F" }, "STATE": { "0": "No State", @@ -392,7 +392,8 @@ "VIEWNAME":"Name", "DATABASE":"Database", "SEQUENCE":"Sequence", - "TIMESTAMP":"Timestamp", + "EVENTTIMESTAMP":"Timestamp", + "LASTSPOOL": "Successful spool", "ACTIONS":"Actions", "CLEAR":"Clear", "CLEARED":"View was successfully cleared!", diff --git a/go.mod b/go.mod index e17500909b..88c19e2625 100644 --- a/go.mod +++ b/go.mod @@ -15,14 +15,16 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/caos/logging v0.0.2 - github.com/caos/oidc v0.13.0 + github.com/caos/oidc v0.13.1 github.com/cockroachdb/cockroach-go/v2 v2.0.8 + github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43 github.com/envoyproxy/protoc-gen-validate v0.4.1 github.com/ghodss/yaml v1.0.0 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.2 // indirect + github.com/google/go-cmp v0.5.3 // indirect github.com/gorilla/csrf v1.7.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 @@ -45,6 +47,8 @@ require ( github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/nicksnyder/go-i18n/v2 v2.1.1 github.com/pquerna/otp v1.2.0 + github.com/prometheus/client_golang v1.8.0 // indirect + github.com/prometheus/common v0.15.0 // indirect github.com/rakyll/statik v0.1.7 github.com/rs/cors v1.7.0 github.com/sony/sonyflake v1.0.0 @@ -54,12 +58,13 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.13.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 go.opentelemetry.io/otel v0.13.0 + go.opentelemetry.io/otel/exporters/metric/prometheus v0.13.0 go.opentelemetry.io/otel/exporters/otlp v0.13.0 go.opentelemetry.io/otel/exporters/stdout v0.13.0 go.opentelemetry.io/otel/sdk v0.13.0 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect - golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect golang.org/x/text v0.3.4 golang.org/x/tools v0.0.0-20201103235415-b653051172e4 google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index bbed0c03a4..4a327f6ca0 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,7 @@ github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v0.13.0 h1:fjKUtfldCPIF4nIzAAj3LzP8Lrd3DuRIMiFdOsj4fLc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v0.13.0/go.mod h1:q/paYxLXKVhwfC3lzLfhtL54fAx14wzMN9DundQOBMc= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= @@ -65,10 +66,19 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb h1:EVl3FJLQCzSbgBezKo/1A4ADnJ4mtJZ0RvnNzDJ44nY= github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= @@ -76,31 +86,67 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= -github.com/caos/oidc v0.13.0 h1:l1IKrqV3HaS2TfseuC5kOR3DdEPfY9AbJXuZ7dsIEQo= -github.com/caos/oidc v0.13.0/go.mod h1:dLvfYUiAt9ORfl77L/KkcWuR/N0ll8Ry1nD2ERsamDY= +github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= +github.com/caos/oidc v0.13.1 h1:8890xd3XJpev5xuU4Dn2l49c1lCA3Qd1xu4KsPSVo38= +github.com/caos/oidc v0.13.1/go.mod h1:dLvfYUiAt9ORfl77L/KkcWuR/N0ll8Ry1nD2ERsamDY= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig= +github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go/v2 v2.0.8 h1:50C/7ptrrfdxDccCjDU0xsdeBca+S0/AYW4Mo8RyzFE= github.com/cockroachdb/cockroach-go/v2 v2.0.8/go.mod h1:nkf7rUmgPdawp3EwRjXIumihI2AYg9usGNWbJ2hsJqI= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -108,8 +154,19 @@ github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9r github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43 h1:eEEfwrmEwl0LVuWz/VkAefdgtPbX174Huu5dxxceihI= +github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43/go.mod h1:/X2OJiJxjQ7alqWZqX9EtBTmZc+4qQ0LvZ1k5wP67RM= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -117,22 +174,44 @@ github.com/envoyproxy/protoc-gen-validate v0.4.1 h1:7dLaJvASGRD7X49jSCSXXHwKPm0Z github.com/envoyproxy/protoc-gen-validate v0.4.1/go.mod h1:E+IEazqdaWv3FrnGtZIu3b9fPFMK8AzeTTrk9SfVwWs= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= +github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -140,6 +219,7 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= @@ -149,6 +229,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -162,30 +243,49 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo= github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -194,34 +294,69 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleinterns/cloud-operations-api-mock v0.0.0-20200709193332-a1e58c29bdd3 h1:eHv/jVY/JNop1xg2J9cBb4EzyMpWZoNCP1BslSAIkOI= github.com/googleinterns/cloud-operations-api-mock v0.0.0-20200709193332-a1e58c29bdd3/go.mod h1:h/KNeRx7oYU4SpA4SoY7W2/NxDKEEVuwA6j9A27L4OI= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y= github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 h1:ux/56T2xqZO/3cP1I2F86qpeoYPCOzk+KF/UH/Ar+lk= github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= @@ -232,34 +367,49 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 h1:KUDFlmBg2buRWNzIcwLlKvfcnujcHQRQ1As1LoaCLAM= github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o= github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M= github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0= github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0= github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= @@ -268,9 +418,21 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E= github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a/go.mod h1:/Pk5i/SqYdYv1cie5wGwoZ4P6TpgMi+Yf58mtJSHdOw= github.com/kevinburke/go.uuid v1.2.0 h1:+1qP8NdkJfgOSTrrrUuA7h0djr1VY77HFXYjR+zUcUo= @@ -279,20 +441,25 @@ github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 h1:KpuDJTaTPQAyWqE github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I= github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac h1:qQ7NAZEHpTyDfmZBH79KEiH3OK47Z+KvYVSR4sS3650= github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac/go.mod h1:Fm9alkN1/LPVY1eqD/psyMwPWE4VWl4P01/nTYZKzBk= +github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -300,85 +467,213 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nicksnyder/go-i18n/v2 v2.1.1 h1:ATCOanRDlrfKVB4WHAdJnLEqZtDmKYsweqsOUYflnBU= github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea h1:jaXWVFZ98/ihXniiDzqNXQgMSgklX4kjfDWZTE3ZtdU= github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4= github.com/spf13/afero v1.3.3 h1:p5gZEKLYoL7wh8VrJesMaYeNxdEd1v3cb4irOk9zB54= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc= github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0= github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w= github.com/ttacon/libphonenumber v1.1.0 h1:tC6kE4t8UI4OqQVQjW5q8gSWhG2wnY5moEpSEORdYm4= github.com/ttacon/libphonenumber v1.1.0/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= @@ -390,6 +685,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0 h1:dnZy1af go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.13.0/go.mod h1:SeQm4RTCcZ2/hlMSTuHb7nwIROe5odBtgfKx+7MMqEs= go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= +go.opentelemetry.io/otel/exporters/metric/prometheus v0.13.0 h1:Jf7AdsEoHKtNTWxXLj/g9XGjsGpdk0otEf0lx00r2Ps= +go.opentelemetry.io/otel/exporters/metric/prometheus v0.13.0/go.mod h1:Tyh3ACxU9a1tu1mF4at7xvNu+BaiPThrr5XZmsoIW7g= go.opentelemetry.io/otel/exporters/otlp v0.13.0 h1:iithmYmMAfLFgCW5TcRXHpXR5NTWO7nGtX3WcBiusVE= go.opentelemetry.io/otel/exporters/otlp v0.13.0/go.mod h1:YHH58UrGcqCKtBkY7sl3zPKpxBzfC1HUUYMRQONJJ9E= go.opentelemetry.io/otel/exporters/stdout v0.13.0 h1:A+XiGIPQbGoJoBOJfKAKnZyiUSjSWvL3XWETUvtom5k= @@ -397,19 +694,33 @@ go.opentelemetry.io/otel/exporters/stdout v0.13.0/go.mod h1:JJt8RpNY6K+ft9ir3iKp go.opentelemetry.io/otel/sdk v0.13.0 h1:4VCfpKamZ8GtnepXxMRurSpHpMKkcxhtO33z1S4rGDQ= go.opentelemetry.io/otel/sdk v0.13.0/go.mod h1:dKvLH8Uu8LcEPlSAUsfW7kMGaJBhk/1NYvpPZ6wIMbU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -422,9 +733,11 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -434,28 +747,38 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -467,16 +790,22 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -486,6 +815,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -494,16 +824,26 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -517,8 +857,11 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX8rnJTxKm9dFPuI68UM= golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -526,30 +869,41 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -557,9 +911,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -568,6 +924,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -575,16 +933,19 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 h1:DFtSed2q3HtNuVazwVDZ4nSRS/JrZEig0gz2BY4VNrg= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4 h1:kDtqNkeBrZb8B+atrj50B5XLHpzXXqcCdZPP/ApQ5NY= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -595,6 +956,7 @@ golang.org/x/tools v0.0.0-20200701151220-7cb253f4c4f8/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201030143252-cf7a54d06671/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -604,9 +966,11 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -617,19 +981,28 @@ google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0 h1:jMF5hhVfMkTZwHW1SDpKq5CkgWLXOb31Foaca9Zr3oM= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.34.0 h1:k40adF3uR+6x/+hO5Dh4ZFUqFp67vxvbpafFiJxl10A= google.golang.org/api v0.34.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -638,6 +1011,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -653,12 +1027,14 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 h1:MRHtG0U6SnaUb+s+LhNE1qt1FQ1wlhqr5E4usBKC0uA= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200605102947-12044bf5ea91/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -666,24 +1042,35 @@ google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6 h1:rMoZiLTOobSD3eg30lPMcFkBFNSyKUQQIQlw/hsAXME= google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= @@ -691,37 +1078,64 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/admin/repository/administrator.go b/internal/admin/repository/administrator.go index f091146a0b..026a293c9e 100644 --- a/internal/admin/repository/administrator.go +++ b/internal/admin/repository/administrator.go @@ -8,6 +8,7 @@ import ( type AdministratorRepository interface { GetFailedEvents(context.Context) ([]*model.FailedEvent, error) RemoveFailedEvent(context.Context, *model.FailedEvent) error - GetViews(context.Context) ([]*model.View, error) - ClearView(ctx context.Context, db, view string) error + GetViews() ([]*model.View, error) + GetSpoolerDiv(db, viewName string) int64 + ClearView(ctx context.Context, db, viewName string) error } diff --git a/internal/admin/repository/eventsourcing/eventstore/administrator.go b/internal/admin/repository/eventsourcing/eventstore/administrator.go index d0af36284e..52ab002cac 100644 --- a/internal/admin/repository/eventsourcing/eventstore/administrator.go +++ b/internal/admin/repository/eventsourcing/eventstore/administrator.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" view_model "github.com/caos/zitadel/internal/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) var dbList = []string{"management", "auth", "authz", "adminapi", "notification"} @@ -31,7 +32,7 @@ func (repo *AdministratorRepo) RemoveFailedEvent(ctx context.Context, failedEven return repo.View.RemoveFailedEvent(failedEvent.Database, repository.FailedEventFromModel(failedEvent)) } -func (repo *AdministratorRepo) GetViews(ctx context.Context) ([]*view_model.View, error) { +func (repo *AdministratorRepo) GetViews() ([]*view_model.View, error) { views := make([]*view_model.View, 0) for _, db := range dbList { sequences, err := repo.View.AllCurrentSequences(db) @@ -45,6 +46,16 @@ func (repo *AdministratorRepo) GetViews(ctx context.Context) ([]*view_model.View return views, nil } +func (repo *AdministratorRepo) GetSpoolerDiv(database, view string) int64 { + sequence, err := repo.View.GetCurrentSequence(database, view) + if err != nil { + + return 0 + } + divDuration := time.Now().Sub(sequence.LastSuccessfulSpoolerRun) + return divDuration.Milliseconds() +} + func (repo *AdministratorRepo) ClearView(ctx context.Context, database, view string) error { return repo.View.ClearView(database, view) } diff --git a/internal/admin/repository/eventsourcing/eventstore/iam.go b/internal/admin/repository/eventsourcing/eventstore/iam.go index 37965c768b..7e04972ec8 100644 --- a/internal/admin/repository/eventsourcing/eventstore/iam.go +++ b/internal/admin/repository/eventsourcing/eventstore/iam.go @@ -15,7 +15,7 @@ import ( iam_es "github.com/caos/zitadel/internal/iam/repository/eventsourcing" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing" iam_business "github.com/caos/zitadel/internal/v2/business/iam" @@ -81,7 +81,7 @@ func (repo *IAMRepository) SearchIAMMembers(ctx context.Context, request *iam_mo } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -206,7 +206,7 @@ func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_mo } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -298,7 +298,7 @@ func (repo *IAMRepository) SearchDefaultIDPProviders(ctx context.Context, reques } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/admin/repository/eventsourcing/eventstore/org.go b/internal/admin/repository/eventsourcing/eventstore/org.go index b0c706b4c6..78400eae29 100644 --- a/internal/admin/repository/eventsourcing/eventstore/org.go +++ b/internal/admin/repository/eventsourcing/eventstore/org.go @@ -6,7 +6,7 @@ import ( "github.com/caos/zitadel/internal/errors" iam_model "github.com/caos/zitadel/internal/iam/model" iam_view "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/logging" admin_model "github.com/caos/zitadel/internal/admin/model" @@ -101,7 +101,7 @@ func (repo *OrgRepo) SearchOrgs(ctx context.Context, query *org_model.OrgSearchR } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 31115d9878..25f3b69223 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -41,7 +41,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev &IDPConfig{handler: handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount}}, &LabelPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount}}, &LoginPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}}, - &IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}, + &IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount}, systemDefaults: defaults, iamEvents: repos.IamEvents, orgEvents: repos.OrgEvents}, &User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, eventstore: eventstore, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents, systemDefaults: defaults}, @@ -49,7 +49,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev &PasswordAgePolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordAgePolicy"), errorCount}}, &PasswordLockoutPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount}}, &OrgIAMPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount}}, - &ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, + &ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount}, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents, systemDefaults: defaults}, } } diff --git a/internal/admin/repository/eventsourcing/handler/iam_member.go b/internal/admin/repository/eventsourcing/handler/iam_member.go index 906e9f6aa3..d3c9b77bb6 100644 --- a/internal/admin/repository/eventsourcing/handler/iam_member.go +++ b/internal/admin/repository/eventsourcing/handler/iam_member.go @@ -72,14 +72,14 @@ func (m *IamMember) processIamMember(event *models.Event) (err error) { if err != nil { return err } - return m.view.DeleteIAMMember(event.AggregateID, member.UserID, event.Sequence) + return m.view.DeleteIAMMember(event.AggregateID, member.UserID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIAMMemberSequence(event.Sequence) + return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIAMMember(member, member.Sequence) + return m.view.PutIAMMember(member, member.Sequence, event.CreationDate) } func (m *IamMember) processUser(event *models.Event) (err error) { @@ -94,7 +94,7 @@ func (m *IamMember) processUser(event *models.Event) (err error) { return err } if len(members) == 0 { - return m.view.ProcessedIAMMemberSequence(event.Sequence) + return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate) } user, err := m.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -103,11 +103,11 @@ func (m *IamMember) processUser(event *models.Event) (err error) { for _, member := range members { m.fillUserData(member, user) } - return m.view.PutIAMMembers(members, event.Sequence) + return m.view.PutIAMMembers(members, event.Sequence, event.CreationDate) case usr_es_model.UserRemoved: - return m.view.DeleteIAMMembersByUserID(event.AggregateID, event.Sequence) + return m.view.DeleteIAMMembersByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIAMMemberSequence(event.Sequence) + return m.view.ProcessedIAMMemberSequence(event.Sequence, event.CreationDate) } return nil } @@ -137,3 +137,7 @@ func (m *IamMember) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Ld9ow", "id", event.AggregateID).WithError(err).Warn("something went wrong in iammember handler") return spooler.HandleError(event, err, m.view.GetLatestIAMMemberFailedEvent, m.view.ProcessedIAMMemberFailedEvent, m.view.ProcessedIAMMemberSequence, m.errorCountUntilSkip) } + +func (m *IamMember) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateIAMMemberSpoolerRunTimestamp) +} diff --git a/internal/admin/repository/eventsourcing/handler/idp_config.go b/internal/admin/repository/eventsourcing/handler/idp_config.go index 1f6500cc0f..be8d43d7dc 100644 --- a/internal/admin/repository/eventsourcing/handler/idp_config.go +++ b/internal/admin/repository/eventsourcing/handler/idp_config.go @@ -19,12 +19,12 @@ const ( idpConfigTable = "adminapi.idp_configs" ) -func (m *IDPConfig) ViewModel() string { +func (i *IDPConfig) ViewModel() string { return idpConfigTable } -func (m *IDPConfig) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestIDPConfigSequence() +func (i *IDPConfig) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestIDPConfigSequence() if err != nil { return nil, err } @@ -33,15 +33,15 @@ func (m *IDPConfig) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *IDPConfig) Reduce(event *models.Event) (err error) { +func (i *IDPConfig) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.IAMAggregate: - err = m.processIDPConfig(event) + err = i.processIDPConfig(event) } return err } -func (m *IDPConfig) processIDPConfig(event *models.Event) (err error) { +func (i *IDPConfig) processIDPConfig(event *models.Event) (err error) { idp := new(iam_view_model.IDPConfigView) switch event.Type { case model.IDPConfigAdded: @@ -53,7 +53,7 @@ func (m *IDPConfig) processIDPConfig(event *models.Event) (err error) { if err != nil { return err } - idp, err = m.view.IDPConfigByID(idp.IDPConfigID) + idp, err = i.view.IDPConfigByID(idp.IDPConfigID) if err != nil { return err } @@ -63,17 +63,21 @@ func (m *IDPConfig) processIDPConfig(event *models.Event) (err error) { if err != nil { return err } - return m.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence) + return i.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIDPConfigSequence(event.Sequence) + return i.view.ProcessedIDPConfigSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIDPConfig(idp, idp.Sequence) + return i.view.PutIDPConfig(idp, idp.Sequence, event.CreationDate) } -func (m *IDPConfig) OnError(event *models.Event, err error) error { +func (i *IDPConfig) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Mslo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler") - return spooler.HandleError(event, err, m.view.GetLatestIDPConfigFailedEvent, m.view.ProcessedIDPConfigFailedEvent, m.view.ProcessedIDPConfigSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestIDPConfigFailedEvent, i.view.ProcessedIDPConfigFailedEvent, i.view.ProcessedIDPConfigSequence, i.errorCountUntilSkip) +} + +func (i *IDPConfig) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateIDPConfigSpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/idp_providers.go b/internal/admin/repository/eventsourcing/handler/idp_providers.go index 0613ae7fa5..0e27ee11ec 100644 --- a/internal/admin/repository/eventsourcing/handler/idp_providers.go +++ b/internal/admin/repository/eventsourcing/handler/idp_providers.go @@ -27,12 +27,12 @@ const ( idpProviderTable = "adminapi.idp_providers" ) -func (m *IDPProvider) ViewModel() string { +func (i *IDPProvider) ViewModel() string { return idpProviderTable } -func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestIDPProviderSequence() +func (i *IDPProvider) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestIDPProviderSequence() if err != nil { return nil, err } @@ -41,15 +41,15 @@ func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *IDPProvider) Reduce(event *models.Event) (err error) { +func (i *IDPProvider) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.IAMAggregate, org_es_model.OrgAggregate: - err = m.processIdpProvider(event) + err = i.processIdpProvider(event) } return err } -func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { +func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) { provider := new(iam_view_model.IDPProviderView) switch event.Type { case model.LoginPolicyIDPProviderAdded, org_es_model.LoginPolicyIDPProviderAdded: @@ -57,64 +57,68 @@ func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { if err != nil { return err } - err = m.fillData(provider) + err = i.fillData(provider) case model.LoginPolicyIDPProviderRemoved, model.LoginPolicyIDPProviderCascadeRemoved, org_es_model.LoginPolicyIDPProviderRemoved, org_es_model.LoginPolicyIDPProviderCascadeRemoved: err = provider.SetData(event) if err != nil { return err } - return m.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence) + return i.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence, event.CreationDate) case model.IDPConfigChanged, org_es_model.IDPConfigChanged: esConfig := new(iam_view_model.IDPConfigView) providerType := iam_model.IDPProviderTypeSystem - if event.AggregateID != m.systemDefaults.IamID { + if event.AggregateID != i.systemDefaults.IamID { providerType = iam_model.IDPProviderTypeOrg } esConfig.AppendEvent(providerType, event) - providers, err := m.view.IDPProvidersByIdpConfigID(esConfig.IDPConfigID) + providers, err := i.view.IDPProvidersByIdpConfigID(esConfig.IDPConfigID) if err != nil { return err } - config, err := m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) + config, err := i.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) if err != nil { return err } for _, provider := range providers { - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) } - return m.view.PutIDPProviders(event.Sequence, providers...) + return i.view.PutIDPProviders(event.Sequence, event.CreationDate, providers...) default: - return m.view.ProcessedIDPProviderSequence(event.Sequence) + return i.view.ProcessedIDPProviderSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIDPProvider(provider, provider.Sequence) + return i.view.PutIDPProvider(provider, provider.Sequence, event.CreationDate) } -func (m *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { +func (i *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { var config *iam_model.IDPConfig if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) { - config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, provider.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), i.systemDefaults.IamID, provider.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID) } if err != nil { return err } - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) return nil } -func (m *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *iam_model.IDPConfig) { +func (i *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *iam_model.IDPConfig) { provider.Name = config.Name provider.StylingType = int32(config.StylingType) provider.IDPConfigType = int32(config.Type) provider.IDPState = int32(config.State) } -func (m *IDPProvider) OnError(event *models.Event, err error) error { +func (i *IDPProvider) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Msj8c", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestIDPProviderFailedEvent, m.view.ProcessedIDPProviderFailedEvent, m.view.ProcessedIDPProviderSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestIDPProviderFailedEvent, i.view.ProcessedIDPProviderFailedEvent, i.view.ProcessedIDPProviderSequence, i.errorCountUntilSkip) +} + +func (i *IDPProvider) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateIDPProviderSpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/label_policy.go b/internal/admin/repository/eventsourcing/handler/label_policy.go index 5226b5e3dd..3f81c0060d 100644 --- a/internal/admin/repository/eventsourcing/handler/label_policy.go +++ b/internal/admin/repository/eventsourcing/handler/label_policy.go @@ -18,12 +18,12 @@ const ( labelPolicyTable = "adminapi.label_policies" ) -func (m *LabelPolicy) ViewModel() string { +func (p *LabelPolicy) ViewModel() string { return labelPolicyTable } -func (m *LabelPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestLabelPolicySequence() +func (p *LabelPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestLabelPolicySequence() if err != nil { return nil, err } @@ -32,35 +32,39 @@ func (m *LabelPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *LabelPolicy) Reduce(event *models.Event) (err error) { +func (p *LabelPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.IAMAggregate: - err = m.processLabelPolicy(event) + err = p.processLabelPolicy(event) } return err } -func (m *LabelPolicy) processLabelPolicy(event *models.Event) (err error) { +func (p *LabelPolicy) processLabelPolicy(event *models.Event) (err error) { policy := new(iam_model.LabelPolicyView) switch event.Type { case model.LabelPolicyAdded: err = policy.AppendEvent(event) case model.LabelPolicyChanged: - policy, err = m.view.LabelPolicyByAggregateID(event.AggregateID) + policy, err = p.view.LabelPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) default: - return m.view.ProcessedLabelPolicySequence(event.Sequence) + return p.view.ProcessedLabelPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutLabelPolicy(policy, policy.Sequence) + return p.view.PutLabelPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *LabelPolicy) OnError(event *models.Event, err error) error { +func (p *LabelPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler") - return spooler.HandleError(event, err, m.view.GetLatestLabelPolicyFailedEvent, m.view.ProcessedLabelPolicyFailedEvent, m.view.ProcessedLabelPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestLabelPolicyFailedEvent, p.view.ProcessedLabelPolicyFailedEvent, p.view.ProcessedLabelPolicySequence, p.errorCountUntilSkip) +} + +func (p *LabelPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateLabelPolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/login_policy.go b/internal/admin/repository/eventsourcing/handler/login_policy.go index 9f4a20988f..c24100ca69 100644 --- a/internal/admin/repository/eventsourcing/handler/login_policy.go +++ b/internal/admin/repository/eventsourcing/handler/login_policy.go @@ -18,12 +18,12 @@ const ( loginPolicyTable = "adminapi.login_policies" ) -func (m *LoginPolicy) ViewModel() string { +func (p *LoginPolicy) ViewModel() string { return loginPolicyTable } -func (m *LoginPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestLoginPolicySequence() +func (p *LoginPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestLoginPolicySequence() if err != nil { return nil, err } @@ -32,15 +32,15 @@ func (m *LoginPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *LoginPolicy) Reduce(event *models.Event) (err error) { +func (p *LoginPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.IAMAggregate: - err = m.processLoginPolicy(event) + err = p.processLoginPolicy(event) } return err } -func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { +func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { policy := new(iam_model.LoginPolicyView) switch event.Type { case model.LoginPolicyAdded: @@ -50,21 +50,25 @@ func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { model.LoginPolicySecondFactorRemoved, model.LoginPolicyMultiFactorAdded, model.LoginPolicyMultiFactorRemoved: - policy, err = m.view.LoginPolicyByAggregateID(event.AggregateID) + policy, err = p.view.LoginPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) default: - return m.view.ProcessedLoginPolicySequence(event.Sequence) + return p.view.ProcessedLoginPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutLoginPolicy(policy, policy.Sequence) + return p.view.PutLoginPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *LoginPolicy) OnError(event *models.Event, err error) error { +func (p *LoginPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") - return spooler.HandleError(event, err, m.view.GetLatestLoginPolicyFailedEvent, m.view.ProcessedLoginPolicyFailedEvent, m.view.ProcessedLoginPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestLoginPolicyFailedEvent, p.view.ProcessedLoginPolicyFailedEvent, p.view.ProcessedLoginPolicySequence, p.errorCountUntilSkip) +} + +func (p *LoginPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/org.go b/internal/admin/repository/eventsourcing/handler/org.go index fafe94a74c..eb64e60ea7 100644 --- a/internal/admin/repository/eventsourcing/handler/org.go +++ b/internal/admin/repository/eventsourcing/handler/org.go @@ -53,13 +53,17 @@ func (o *Org) Reduce(event *es_models.Event) error { return err } default: - return o.view.ProcessedOrgSequence(event.Sequence) + return o.view.ProcessedOrgSequence(event.Sequence, event.CreationDate) } - return o.view.PutOrg(org) + return o.view.PutOrg(org, event.CreationDate) } func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in project app handler") return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) } + +func (o *Org) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) +} diff --git a/internal/admin/repository/eventsourcing/handler/org_iam_policy.go b/internal/admin/repository/eventsourcing/handler/org_iam_policy.go index cb49e33efb..99c01e73e1 100644 --- a/internal/admin/repository/eventsourcing/handler/org_iam_policy.go +++ b/internal/admin/repository/eventsourcing/handler/org_iam_policy.go @@ -19,12 +19,12 @@ const ( orgIAMPolicyTable = "adminapi.org_iam_policies" ) -func (m *OrgIAMPolicy) ViewModel() string { +func (p *OrgIAMPolicy) ViewModel() string { return orgIAMPolicyTable } -func (m *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestOrgIAMPolicySequence() +func (p *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestOrgIAMPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *OrgIAMPolicy) Reduce(event *models.Event) (err error) { +func (p *OrgIAMPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processOrgIAMPolicy(event) + err = p.processOrgIAMPolicy(event) } return err } -func (m *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) { +func (p *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) { policy := new(iam_model.OrgIAMPolicyView) switch event.Type { case iam_es_model.OrgIAMPolicyAdded, model.OrgIAMPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.OrgIAMPolicyChanged, model.OrgIAMPolicyChanged: - policy, err = m.view.OrgIAMPolicyByAggregateID(event.AggregateID) + policy, err = p.view.OrgIAMPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.OrgIAMPolicyRemoved: - return m.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence) + return p.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedOrgIAMPolicySequence(event.Sequence) + return p.view.ProcessedOrgIAMPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutOrgIAMPolicy(policy, policy.Sequence) + return p.view.PutOrgIAMPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *OrgIAMPolicy) OnError(event *models.Event, err error) error { +func (p *OrgIAMPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Wm8fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgIAM policy handler") - return spooler.HandleError(event, err, m.view.GetLatestOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicySequence, p.errorCountUntilSkip) +} + +func (p *OrgIAMPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateOrgIAMPolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/password_age_policy.go b/internal/admin/repository/eventsourcing/handler/password_age_policy.go index bf56af86ef..3608ec446e 100644 --- a/internal/admin/repository/eventsourcing/handler/password_age_policy.go +++ b/internal/admin/repository/eventsourcing/handler/password_age_policy.go @@ -19,12 +19,12 @@ const ( passwordAgePolicyTable = "adminapi.password_age_policies" ) -func (m *PasswordAgePolicy) ViewModel() string { +func (p *PasswordAgePolicy) ViewModel() string { return passwordAgePolicyTable } -func (m *PasswordAgePolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordAgePolicySequence() +func (p *PasswordAgePolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordAgePolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordAgePolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordAgePolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordAgePolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordAgePolicy(event) + err = p.processPasswordAgePolicy(event) } return err } -func (m *PasswordAgePolicy) processPasswordAgePolicy(event *models.Event) (err error) { +func (p *PasswordAgePolicy) processPasswordAgePolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordAgePolicyView) switch event.Type { case iam_es_model.PasswordAgePolicyAdded, model.PasswordAgePolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordAgePolicyChanged, model.PasswordAgePolicyChanged: - policy, err = m.view.PasswordAgePolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordAgePolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordAgePolicyRemoved: - return m.view.DeletePasswordAgePolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordAgePolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordAgePolicySequence(event.Sequence) + return p.view.ProcessedPasswordAgePolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordAgePolicy(policy, policy.Sequence) + return p.view.PutPasswordAgePolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordAgePolicy) OnError(event *models.Event, err error) error { +func (p *PasswordAgePolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-nD8sie", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordAge policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordAgePolicyFailedEvent, m.view.ProcessedPasswordAgePolicyFailedEvent, m.view.ProcessedPasswordAgePolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordAgePolicyFailedEvent, p.view.ProcessedPasswordAgePolicyFailedEvent, p.view.ProcessedPasswordAgePolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordAgePolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProcessedPasswordAgePolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/password_complexity_policy.go b/internal/admin/repository/eventsourcing/handler/password_complexity_policy.go index 2eeb6cf759..f1d2898218 100644 --- a/internal/admin/repository/eventsourcing/handler/password_complexity_policy.go +++ b/internal/admin/repository/eventsourcing/handler/password_complexity_policy.go @@ -19,12 +19,12 @@ const ( passwordComplexityPolicyTable = "adminapi.password_complexity_policies" ) -func (m *PasswordComplexityPolicy) ViewModel() string { +func (p *PasswordComplexityPolicy) ViewModel() string { return passwordComplexityPolicyTable } -func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordComplexityPolicySequence() +func (p *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordComplexityPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordComplexityPolicy(event) + err = p.processPasswordComplexityPolicy(event) } return err } -func (m *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordComplexityPolicyView) switch event.Type { case iam_es_model.PasswordComplexityPolicyAdded, model.PasswordComplexityPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordComplexityPolicyChanged, model.PasswordComplexityPolicyChanged: - policy, err = m.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordComplexityPolicyRemoved: - return m.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordComplexityPolicySequence(event.Sequence) + return p.view.ProcessedPasswordComplexityPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordComplexityPolicy(policy, policy.Sequence) + return p.view.PutPasswordComplexityPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { +func (p *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Wm8fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordComplexity policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordComplexityPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdatePasswordComplexityPolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/password_lockout_policy.go b/internal/admin/repository/eventsourcing/handler/password_lockout_policy.go index bd71ab054d..fd087adda7 100644 --- a/internal/admin/repository/eventsourcing/handler/password_lockout_policy.go +++ b/internal/admin/repository/eventsourcing/handler/password_lockout_policy.go @@ -19,12 +19,12 @@ const ( passwordLockoutPolicyTable = "adminapi.password_lockout_policies" ) -func (m *PasswordLockoutPolicy) ViewModel() string { +func (p *PasswordLockoutPolicy) ViewModel() string { return passwordLockoutPolicyTable } -func (m *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordLockoutPolicySequence() +func (p *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordLockoutPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordLockoutPolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordLockoutPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordLockoutPolicy(event) + err = p.processPasswordLockoutPolicy(event) } return err } -func (m *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *models.Event) (err error) { +func (p *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordLockoutPolicyView) switch event.Type { case iam_es_model.PasswordLockoutPolicyAdded, model.PasswordLockoutPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordLockoutPolicyChanged, model.PasswordLockoutPolicyChanged: - policy, err = m.view.PasswordLockoutPolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordLockoutPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordLockoutPolicyRemoved: - return m.view.DeletePasswordLockoutPolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordLockoutPolicySequence(event.Sequence) + return p.view.ProcessedPasswordLockoutPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordLockoutPolicy(policy, policy.Sequence) + return p.view.PutPasswordLockoutPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordLockoutPolicy) OnError(event *models.Event, err error) error { +func (p *PasswordLockoutPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-nD8sie", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordLockoutPolicyFailedEvent, m.view.ProcessedPasswordLockoutPolicyFailedEvent, m.view.ProcessedPasswordLockoutPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordLockoutPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdatePasswordLockoutPolicySpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/handler/user.go b/internal/admin/repository/eventsourcing/handler/user.go index 8a4105d131..f0f26afc4f 100644 --- a/internal/admin/repository/eventsourcing/handler/user.go +++ b/internal/admin/repository/eventsourcing/handler/user.go @@ -92,6 +92,12 @@ func (u *User) ProcessUser(event *models.Event) (err error) { es_model.HumanMFAOTPAdded, es_model.HumanMFAOTPVerified, es_model.HumanMFAOTPRemoved, + es_model.HumanMFAU2FTokenAdded, + es_model.HumanMFAU2FTokenVerified, + es_model.HumanMFAU2FTokenRemoved, + es_model.HumanPasswordlessTokenAdded, + es_model.HumanPasswordlessTokenVerified, + es_model.HumanPasswordlessTokenRemoved, es_model.MachineChanged: user, err = u.view.UserByID(event.AggregateID) if err != nil { @@ -110,14 +116,14 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - return u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutUser(user, user.Sequence) + return u.view.PutUser(user, user.Sequence, event.CreationDate) } func (u *User) ProcessOrg(event *models.Event) (err error) { @@ -131,7 +137,7 @@ func (u *User) ProcessOrg(event *models.Event) (err error) { case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } } @@ -154,7 +160,7 @@ func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.SetLoginNames(policy, org.Domains) } - return u.view.PutUsers(users, event.Sequence) + return u.view.PutUsers(users, event.Sequence, event.CreationDate) } func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { @@ -179,7 +185,7 @@ func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain) } - return u.view.PutUsers(users, event.Sequence) + return u.view.PutUsers(users, event.Sequence, event.CreationDate) } func (u *User) fillLoginNames(user *view_model.UserView) (err error) { @@ -203,3 +209,7 @@ func (u *User) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-vLmwQ", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler") return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip) } + +func (u *User) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserSpoolerRunTimestamp) +} diff --git a/internal/admin/repository/eventsourcing/handler/user_external_idps.go b/internal/admin/repository/eventsourcing/handler/user_external_idps.go index b9c74c1c89..cc4d979dc3 100644 --- a/internal/admin/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/admin/repository/eventsourcing/handler/user_external_idps.go @@ -30,12 +30,12 @@ const ( externalIDPTable = "adminapi.user_external_idps" ) -func (m *ExternalIDP) ViewModel() string { +func (i *ExternalIDP) ViewModel() string { return externalIDPTable } -func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestExternalIDPSequence() +func (i *ExternalIDP) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestExternalIDPSequence() if err != nil { return nil, err } @@ -44,17 +44,17 @@ func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *ExternalIDP) Reduce(event *models.Event) (err error) { +func (i *ExternalIDP) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.UserAggregate: - err = m.processUser(event) + err = i.processUser(event) case iam_es_model.IAMAggregate, org_es_model.OrgAggregate: - err = m.processIdpConfig(event) + err = i.processIdpConfig(event) } return err } -func (m *ExternalIDP) processUser(event *models.Event) (err error) { +func (i *ExternalIDP) processUser(event *models.Event) (err error) { externalIDP := new(usr_view_model.ExternalIDPView) switch event.Type { case model.HumanExternalIDPAdded: @@ -62,25 +62,25 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { if err != nil { return err } - err = m.fillData(externalIDP) + err = i.fillData(externalIDP) case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved: err = externalIDP.SetData(event) if err != nil { return err } - return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence, event.CreationDate) case model.UserRemoved: - return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) + return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence) + return i.view.PutExternalIDP(externalIDP, externalIDP.Sequence, event.CreationDate) } -func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { +func (i *ExternalIDP) processIdpConfig(event *models.Event) (err error) { switch event.Type { case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: configView := new(iam_view_model.IDPConfigView) @@ -90,45 +90,49 @@ func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { } else { configView.AppendEvent(iam_model.IDPProviderTypeOrg, event) } - exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) + exterinalIDPs, err := i.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) if err != nil { return err } if event.AggregateType == iam_es_model.IAMAggregate { - config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } if err != nil { return err } for _, provider := range exterinalIDPs { - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) } - return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...) + return i.view.PutExternalIDPs(event.Sequence, event.CreationDate, exterinalIDPs...) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } return nil } -func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { - config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) +func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { + config, err := i.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) if caos_errs.IsNotFound(err) { - config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), i.systemDefaults.IamID, externalIDP.IDPConfigID) } if err != nil { return err } - m.fillConfigData(externalIDP, config) + i.fillConfigData(externalIDP, config) return nil } -func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { +func (i *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { externalIDP.IDPName = config.Name } -func (m *ExternalIDP) OnError(event *models.Event, err error) error { +func (i *ExternalIDP) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestExternalIDPFailedEvent, i.view.ProcessedExternalIDPFailedEvent, i.view.ProcessedExternalIDPSequence, i.errorCountUntilSkip) +} + +func (i *ExternalIDP) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateExternalIDPSpoolerRunTimestamp) } diff --git a/internal/admin/repository/eventsourcing/view/external_idps.go b/internal/admin/repository/eventsourcing/view/external_idps.go index de919a589a..8f21e7de86 100644 --- a/internal/admin/repository/eventsourcing/view/external_idps.go +++ b/internal/admin/repository/eventsourcing/view/external_idps.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -32,44 +33,48 @@ func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ( return view.SearchExternalIDPs(v.Db, externalIDPTable, request) } -func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error { +func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64, eventTimestamp time.Time) error { err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error { +func (v *View) PutExternalIDPs(sequence uint64, eventTimestamp time.Time, externalIDPs ...*model.ExternalIDPView) error { err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) if err != nil { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } -func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error { - return v.saveCurrentSequence(externalIDPTable, eventSequence) +func (v *View) ProcessedExternalIDPSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(externalIDPTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateExternalIDPSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(externalIDPTable) } func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/iam_member.go b/internal/admin/repository/eventsourcing/view/iam_member.go index 2e27aa4182..250a0042fa 100644 --- a/internal/admin/repository/eventsourcing/view/iam_member.go +++ b/internal/admin/repository/eventsourcing/view/iam_member.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -24,44 +25,48 @@ func (v *View) IAMMembersByUserID(userID string) ([]*model.IAMMemberView, error) return view.IAMMembersByUserID(v.Db, iamMemberTable, userID) } -func (v *View) PutIAMMember(org *model.IAMMemberView, sequence uint64) error { +func (v *View) PutIAMMember(org *model.IAMMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIAMMember(v.Db, iamMemberTable, org) if err != nil { return err } - return v.ProcessedIAMMemberSequence(sequence) + return v.ProcessedIAMMemberSequence(sequence, eventTimestamp) } -func (v *View) PutIAMMembers(members []*model.IAMMemberView, sequence uint64) error { +func (v *View) PutIAMMembers(members []*model.IAMMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIAMMembers(v.Db, iamMemberTable, members...) if err != nil { return err } - return v.ProcessedIAMMemberSequence(sequence) + return v.ProcessedIAMMemberSequence(sequence, eventTimestamp) } -func (v *View) DeleteIAMMember(iamID, userID string, eventSequence uint64) error { +func (v *View) DeleteIAMMember(iamID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIAMMember(v.Db, iamMemberTable, iamID, userID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIAMMemberSequence(eventSequence) + return v.ProcessedIAMMemberSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteIAMMembersByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteIAMMembersByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIAMMembersByUserID(v.Db, iamMemberTable, userID) if err != nil { return err } - return v.ProcessedIAMMemberSequence(eventSequence) + return v.ProcessedIAMMemberSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIAMMemberSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(iamMemberTable) } -func (v *View) ProcessedIAMMemberSequence(eventSequence uint64) error { - return v.saveCurrentSequence(iamMemberTable, eventSequence) +func (v *View) ProcessedIAMMemberSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(iamMemberTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIAMMemberSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(iamMemberTable) } func (v *View) GetLatestIAMMemberFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/idp_configs.go b/internal/admin/repository/eventsourcing/view/idp_configs.go index d6deab1212..197c37d09b 100644 --- a/internal/admin/repository/eventsourcing/view/idp_configs.go +++ b/internal/admin/repository/eventsourcing/view/idp_configs.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -20,28 +21,32 @@ func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*m return view.SearchIDPs(v.Db, idpConfigTable, request) } -func (v *View) PutIDPConfig(idp *model.IDPConfigView, sequence uint64) error { +func (v *View) PutIDPConfig(idp *model.IDPConfigView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDP(v.Db, idpConfigTable, idp) if err != nil { return err } - return v.ProcessedIDPConfigSequence(sequence) + return v.ProcessedIDPConfigSequence(sequence, eventTimestamp) } -func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64) error { +func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDP(v.Db, idpConfigTable, idpID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPConfigSequence(eventSequence) + return v.ProcessedIDPConfigSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIDPConfigSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpConfigTable) } -func (v *View) ProcessedIDPConfigSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpConfigTable, eventSequence) +func (v *View) ProcessedIDPConfigSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpConfigTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIDPConfigSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpConfigTable) } func (v *View) GetLatestIDPConfigFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/idp_providers.go b/internal/admin/repository/eventsourcing/view/idp_providers.go index 5c4931abf6..10b5886945 100644 --- a/internal/admin/repository/eventsourcing/view/idp_providers.go +++ b/internal/admin/repository/eventsourcing/view/idp_providers.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -24,36 +25,40 @@ func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) ( return view.SearchIDPProviders(v.Db, idpProviderTable, request) } -func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64) error { +func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDPProvider(v.Db, idpProviderTable, provider) if err != nil { return err } - return v.ProcessedIDPProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) PutIDPProviders(sequence uint64, providers ...*model.IDPProviderView) error { +func (v *View) PutIDPProviders(sequence uint64, eventTimestamp time.Time, providers ...*model.IDPProviderView) error { err := view.PutIDPProviders(v.Db, idpProviderTable, providers...) if err != nil { return err } - return v.ProcessedIDPProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPProviderSequence(eventSequence) + return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIDPProviderSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpProviderTable) } -func (v *View) ProcessedIDPProviderSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpProviderTable, eventSequence) +func (v *View) ProcessedIDPProviderSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpProviderTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIDPProviderSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpProviderTable) } func (v *View) GetLatestIDPProviderFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/label_policies.go b/internal/admin/repository/eventsourcing/view/label_policies.go index 113859894f..f4e921ac1c 100644 --- a/internal/admin/repository/eventsourcing/view/label_policies.go +++ b/internal/admin/repository/eventsourcing/view/label_policies.go @@ -4,6 +4,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -14,20 +15,24 @@ func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyV return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID) } -func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, sequence uint64) error { +func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutLabelPolicy(v.Db, labelPolicyTable, policy) if err != nil { return err } - return v.ProcessedLabelPolicySequence(sequence) + return v.ProcessedLabelPolicySequence(sequence, eventTimestamp) } func (v *View) GetLatestLabelPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(labelPolicyTable) } -func (v *View) ProcessedLabelPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(labelPolicyTable, eventSequence) +func (v *View) ProcessedLabelPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(labelPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateLabelPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(labelPolicyTable) } func (v *View) GetLatestLabelPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/login_policies.go b/internal/admin/repository/eventsourcing/view/login_policies.go index 8e37e57729..03e4451ffb 100644 --- a/internal/admin/repository/eventsourcing/view/login_policies.go +++ b/internal/admin/repository/eventsourcing/view/login_policies.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyV return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64) error { +func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) if err != nil { return err } - return v.ProcessedLoginPolicySequence(sequence) + return v.ProcessedLoginPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedLoginPolicySequence(eventSequence) + return v.ProcessedLoginPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(loginPolicyTable) } -func (v *View) ProcessedLoginPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(loginPolicyTable, eventSequence) +func (v *View) ProcessedLoginPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(loginPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(loginPolicyTable) } func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/org.go b/internal/admin/repository/eventsourcing/view/org.go index 2c580ab012..ee9f9fb113 100644 --- a/internal/admin/repository/eventsourcing/view/org.go +++ b/internal/admin/repository/eventsourcing/view/org.go @@ -5,6 +5,7 @@ import ( org_view "github.com/caos/zitadel/internal/org/repository/view" "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -19,12 +20,12 @@ func (v *View) SearchOrgs(query *org_model.OrgSearchRequest) ([]*model.OrgView, return org_view.SearchOrgs(v.Db, orgTable, query) } -func (v *View) PutOrg(org *model.OrgView) error { +func (v *View) PutOrg(org *model.OrgView, eventTimestamp time.Time) error { err := org_view.PutOrg(v.Db, orgTable, org) if err != nil { return err } - return v.ProcessedOrgSequence(org.Sequence) + return v.ProcessedOrgSequence(org.Sequence, eventTimestamp) } func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { @@ -35,10 +36,14 @@ func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) erro return v.saveFailedEvent(failedEvent) } +func (v *View) UpdateOrgSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgTable) +} + func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgTable) } -func (v *View) ProcessedOrgSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgTable, eventSequence) +func (v *View) ProcessedOrgSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgTable, eventSequence, eventTimestamp) } diff --git a/internal/admin/repository/eventsourcing/view/org_iam_policy.go b/internal/admin/repository/eventsourcing/view/org_iam_policy.go index 57196f7eaf..3168a78152 100644 --- a/internal/admin/repository/eventsourcing/view/org_iam_policy.go +++ b/internal/admin/repository/eventsourcing/view/org_iam_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) OrgIAMPolicyByAggregateID(aggregateID string) (*model.OrgIAMPolic return view.GetOrgIAMPolicyByAggregateID(v.Db, orgIAMPolicyTable, aggregateID) } -func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64) error { +func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgIAMPolicy(v.Db, orgIAMPolicyTable, policy) if err != nil { return err } - return v.ProcessedOrgIAMPolicySequence(sequence) + return v.ProcessedOrgIAMPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgIAMPolicy(v.Db, orgIAMPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedOrgIAMPolicySequence(eventSequence) + return v.ProcessedOrgIAMPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestOrgIAMPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(orgIAMPolicyTable) } -func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence) +func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgIAMPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgIAMPolicyTable) } func (v *View) GetLatestOrgIAMPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/password_age_policy.go b/internal/admin/repository/eventsourcing/view/password_age_policy.go index 915f2cb50d..419b537830 100644 --- a/internal/admin/repository/eventsourcing/view/password_age_policy.go +++ b/internal/admin/repository/eventsourcing/view/password_age_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordAgePolicyByAggregateID(aggregateID string) (*model.Passwo return view.GetPasswordAgePolicyByAggregateID(v.Db, passwordAgePolicyTable, aggregateID) } -func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, sequence uint64) error { +func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordAgePolicy(v.Db, passwordAgePolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordAgePolicySequence(sequence) + return v.ProcessedPasswordAgePolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordAgePolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordAgePolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordAgePolicy(v.Db, passwordAgePolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordAgePolicySequence(eventSequence) + return v.ProcessedPasswordAgePolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordAgePolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordAgePolicyTable) } -func (v *View) ProcessedPasswordAgePolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordAgePolicyTable, eventSequence) +func (v *View) ProcessedPasswordAgePolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordAgePolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProcessedPasswordAgePolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordAgePolicyTable) } func (v *View) GetLatestPasswordAgePolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/password_complexity_policy.go b/internal/admin/repository/eventsourcing/view/password_complexity_policy.go index 554a32ca0d..2b1af7d375 100644 --- a/internal/admin/repository/eventsourcing/view/password_complexity_policy.go +++ b/internal/admin/repository/eventsourcing/view/password_complexity_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordComplexityPolicyByAggregateID(aggregateID string) (*model return view.GetPasswordComplexityPolicyByAggregateID(v.Db, passwordComplexityPolicyTable, aggregateID) } -func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64) error { +func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordComplexityPolicySequence(sequence) + return v.ProcessedPasswordComplexityPolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordComplexityPolicySequence(eventSequence) + return v.ProcessedPasswordComplexityPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordComplexityPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordComplexityPolicyTable) } -func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence) +func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordComplexityPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordComplexityPolicyTable) } func (v *View) GetLatestPasswordComplexityPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/password_lockout_policy.go b/internal/admin/repository/eventsourcing/view/password_lockout_policy.go index 4ee6d38623..d20643d37c 100644 --- a/internal/admin/repository/eventsourcing/view/password_lockout_policy.go +++ b/internal/admin/repository/eventsourcing/view/password_lockout_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordLockoutPolicyByAggregateID(aggregateID string) (*model.Pa return view.GetPasswordLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID) } -func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, sequence uint64) error { +func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordLockoutPolicySequence(sequence) + return v.ProcessedPasswordLockoutPolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordLockoutPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordLockoutPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordLockoutPolicySequence(eventSequence) + return v.ProcessedPasswordLockoutPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordLockoutPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordLockoutPolicyTable) } -func (v *View) ProcessedPasswordLockoutPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordLockoutPolicyTable, eventSequence) +func (v *View) ProcessedPasswordLockoutPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordLockoutPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordLockoutPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordLockoutPolicyTable) } func (v *View) GetLatestPasswordLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/admin/repository/eventsourcing/view/sequence.go b/internal/admin/repository/eventsourcing/view/sequence.go index 3d01519477..cda4580a6b 100644 --- a/internal/admin/repository/eventsourcing/view/sequence.go +++ b/internal/admin/repository/eventsourcing/view/sequence.go @@ -2,14 +2,15 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( sequencesTable = "adminapi.current_sequences" ) -func (v *View) saveCurrentSequence(viewName string, sequence uint64) error { - return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence) +func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimeStamp time.Time) error { + return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimeStamp) } func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) { @@ -20,6 +21,24 @@ func (v *View) AllCurrentSequences(db string) ([]*repository.CurrentSequence, er return repository.AllCurrentSequences(v.Db, db+".current_sequences") } +func (v *View) updateSpoolerRunSequence(viewName string) error { + currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName) + if err != nil { + return err + } + if currentSequence.ViewName == "" { + currentSequence.ViewName = viewName + } + currentSequence.LastSuccessfulSpoolerRun = time.Now() + return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence) +} + +func (v *View) GetCurrentSequence(db, viewName string) (*repository.CurrentSequence, error) { + sequenceTable := db + ".current_sequences" + fullView := db + "." + viewName + return repository.LatestSequence(v.Db, sequenceTable, fullView) +} + func (v *View) ClearView(db, viewName string) error { truncateView := db + "." + viewName sequenceTable := db + ".current_sequences" diff --git a/internal/admin/repository/eventsourcing/view/user.go b/internal/admin/repository/eventsourcing/view/user.go index 1dcdcebc5f..3de68472cf 100644 --- a/internal/admin/repository/eventsourcing/view/user.go +++ b/internal/admin/repository/eventsourcing/view/user.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -35,43 +36,47 @@ func (v *View) IsUserUnique(userName, email string) (bool, error) { return view.IsUserUnique(v.Db, userTable, userName, email) } -func (v *View) UserMfas(userID string) ([]*usr_model.MultiFactor, error) { - return view.UserMfas(v.Db, userTable, userID) +func (v *View) UserMFAs(userID string) ([]*usr_model.MultiFactor, error) { + return view.UserMFAs(v.Db, userTable, userID) } -func (v *View) PutUsers(user []*model.UserView, sequence uint64) error { +func (v *View) PutUsers(user []*model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUsers(v.Db, userTable, user...) if err != nil { return err } - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } -func (v *View) PutUser(user *model.UserView, sequence uint64) error { +func (v *View) PutUser(user *model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUser(v.Db, userTable, user) if err != nil { return err } if sequence != 0 { - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } return nil } -func (v *View) DeleteUser(userID string, eventSequence uint64) error { +func (v *View) DeleteUser(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUser(v.Db, userTable, userID) if err != nil { return nil } - return v.ProcessedUserSequence(eventSequence) + return v.ProcessedUserSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userTable) } -func (v *View) ProcessedUserSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userTable, eventSequence) +func (v *View) ProcessedUserSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userTable) } func (v *View) GetLatestUserFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/api/api.go b/internal/api/api.go index 7de3ff62ba..a0225af53a 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -2,6 +2,12 @@ package api import ( "context" + admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing" + auth_es "github.com/caos/zitadel/internal/auth/repository/eventsourcing" + "github.com/caos/zitadel/internal/telemetry/metrics" + "github.com/caos/zitadel/internal/telemetry/metrics/otel" + view_model "github.com/caos/zitadel/internal/view/model" + "go.opentelemetry.io/otel/api/metric" "net/http" "github.com/caos/logging" @@ -16,7 +22,7 @@ import ( "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/errors" iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type Config struct { @@ -30,19 +36,33 @@ type API struct { verifier *authz.TokenVerifier serverPort string health health + auth auth + admin admin } + type health interface { Health(ctx context.Context) error IamByID(ctx context.Context) (*iam_model.IAM, error) VerifierClientID(ctx context.Context, appName string) (string, error) } -func Create(config Config, authZ authz.Config, authZRepo *authz_es.EsRepository, sd systemdefaults.SystemDefaults) *API { +type auth interface { + ActiveUserSessionCount() int64 +} + +type admin interface { + GetViews() ([]*view_model.View, error) + GetSpoolerDiv(database, viewName string) int64 +} + +func Create(config Config, authZ authz.Config, authZRepo *authz_es.EsRepository, authRepo *auth_es.EsRepository, adminRepo *admin_es.EsRepository, sd systemdefaults.SystemDefaults) *API { api := &API{ serverPort: config.GRPC.ServerPort, } api.verifier = authz.Start(authZRepo) api.health = authZRepo + api.auth = authRepo + api.admin = adminRepo api.grpcServer = server.CreateServer(api.verifier, authZ, sd.DefaultLanguage) api.gatewayHandler = server.CreateGatewayHandler(config.GRPC) api.RegisterHandler("", api.healthHandler()) @@ -92,6 +112,7 @@ func (a *API) healthHandler() http.Handler { handler.HandleFunc("/ready", handleReadiness(checks)) handler.HandleFunc("/validate", handleValidate(checks)) handler.HandleFunc("/clientID", a.handleClientID) + handler.Handle("/metrics", a.handleMetrics()) return handler } @@ -132,6 +153,48 @@ func (a *API) handleClientID(w http.ResponseWriter, r *http.Request) { http_util.MarshalJSON(w, id, nil, http.StatusOK) } +func (a *API) handleMetrics() http.Handler { + a.registerActiveSessionCounters() + a.registerSpoolerDivCounters() + return metrics.GetExporter() +} + +func (a *API) registerActiveSessionCounters() { + metrics.RegisterValueObserver( + metrics.ActiveSessionCounter, + metrics.ActiveSessionCounterDescription, + func(ctx context.Context, result metric.Int64ObserverResult) { + result.Observe( + a.auth.ActiveUserSessionCount(), + ) + }, + ) +} + +func (a *API) registerSpoolerDivCounters() { + views, err := a.admin.GetViews() + if err != nil { + logging.Log("API-3M8sd").WithError(err).Error("could not read views for metrics") + return + } + metrics.RegisterValueObserver( + metrics.SpoolerDivCounter, + metrics.SpoolerDivCounterDescription, + func(ctx context.Context, result metric.Int64ObserverResult) { + for _, view := range views { + labels := map[string]interface{}{ + metrics.Database: view.Database, + metrics.ViewName: view.ViewName, + } + result.Observe( + a.admin.GetSpoolerDiv(view.Database, view.ViewName), + otel.MapToKeyValue(labels)..., + ) + } + }, + ) +} + type ValidationFunction func(ctx context.Context) error func validate(ctx context.Context, validations []ValidationFunction) []error { diff --git a/internal/api/authz/authorization.go b/internal/api/authz/authorization.go index 740bff3431..7ae5e838f4 100644 --- a/internal/api/authz/authorization.go +++ b/internal/api/authz/authorization.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( diff --git a/internal/api/authz/context.go b/internal/api/authz/context.go index 576fe2c13a..4880a7373e 100644 --- a/internal/api/authz/context.go +++ b/internal/api/authz/context.go @@ -6,7 +6,7 @@ import ( "github.com/caos/zitadel/internal/api/grpc" http_util "github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type key int diff --git a/internal/api/authz/permissions.go b/internal/api/authz/permissions.go index 6147deeac9..8023152ca9 100644 --- a/internal/api/authz/permissions.go +++ b/internal/api/authz/permissions.go @@ -4,7 +4,7 @@ import ( "context" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) func getUserMethodPermissions(ctx context.Context, t *TokenVerifier, requiredPerm string, authConfig Config) (_ context.Context, _ []string, err error) { diff --git a/internal/api/authz/token.go b/internal/api/authz/token.go index a646cdb8ef..463b7396c2 100644 --- a/internal/api/authz/token.go +++ b/internal/api/authz/token.go @@ -6,7 +6,7 @@ import ( "sync" caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( diff --git a/internal/api/grpc/admin/administrator.go b/internal/api/grpc/admin/administrator.go index 58fbefaf6c..1d855a61f2 100644 --- a/internal/api/grpc/admin/administrator.go +++ b/internal/api/grpc/admin/administrator.go @@ -10,7 +10,7 @@ import ( ) func (s *Server) GetViews(ctx context.Context, _ *empty.Empty) (_ *admin.Views, err error) { - views, err := s.administrator.GetViews(ctx) + views, err := s.administrator.GetViews() if err != nil { return nil, err } diff --git a/internal/api/grpc/admin/administrator_converter.go b/internal/api/grpc/admin/administrator_converter.go index 7dc640a8a4..1f93d2496c 100644 --- a/internal/api/grpc/admin/administrator_converter.go +++ b/internal/api/grpc/admin/administrator_converter.go @@ -26,14 +26,17 @@ func failedEventsFromModel(failedEvents []*view_model.FailedEvent) []*admin.Fail } func viewFromModel(view *view_model.View) *admin.View { - timestamp, err := ptypes.TimestampProto(view.CurrentTimestamp) + eventTimestamp, err := ptypes.TimestampProto(view.EventTimestamp) + logging.Log("GRPC-KSo03").OnError(err).Debug("unable to parse timestamp") + lastSpool, err := ptypes.TimestampProto(view.EventTimestamp) logging.Log("GRPC-KSo03").OnError(err).Debug("unable to parse timestamp") return &admin.View{ - Database: view.Database, - ViewName: view.ViewName, - ProcessedSequence: view.CurrentSequence, - ViewTimestamp: timestamp, + Database: view.Database, + ViewName: view.ViewName, + ProcessedSequence: view.CurrentSequence, + EventTimestamp: eventTimestamp, + LastSuccessfulSpoolerRun: lastSpool, } } diff --git a/internal/api/grpc/admin/login_policy_converter.go b/internal/api/grpc/admin/login_policy_converter.go index 90a5ccc415..d45af029d4 100644 --- a/internal/api/grpc/admin/login_policy_converter.go +++ b/internal/api/grpc/admin/login_policy_converter.go @@ -13,6 +13,7 @@ func loginPolicyToModel(policy *admin.DefaultLoginPolicyRequest) *iam_model.Logi AllowExternalIdp: policy.AllowExternalIdp, AllowRegister: policy.AllowRegister, ForceMFA: policy.ForceMfa, + PasswordlessType: passwordlessTypeToModel(policy.PasswordlessType), } } @@ -28,6 +29,7 @@ func loginPolicyFromModel(policy *iam_model.LoginPolicy) *admin.DefaultLoginPoli AllowExternalIdp: policy.AllowExternalIdp, AllowRegister: policy.AllowRegister, ForceMfa: policy.ForceMFA, + PasswordlessType: passwordlessTypeFromModel(policy.PasswordlessType), CreationDate: creationDate, ChangeDate: changeDate, } @@ -45,6 +47,7 @@ func loginPolicyViewFromModel(policy *iam_model.LoginPolicyView) *admin.DefaultL AllowExternalIdp: policy.AllowExternalIDP, AllowRegister: policy.AllowRegister, ForceMfa: policy.ForceMFA, + PasswordlessType: passwordlessTypeFromModel(policy.PasswordlessType), CreationDate: creationDate, ChangeDate: changeDate, } @@ -145,6 +148,24 @@ func secondFactorTypeToModel(mfaType *admin.SecondFactor) iam_model.SecondFactor } } +func passwordlessTypeFromModel(passwordlessType iam_model.PasswordlessType) admin.PasswordlessType { + switch passwordlessType { + case iam_model.PasswordlessTypeAllowed: + return admin.PasswordlessType_PASSWORDLESSTYPE_ALLOWED + default: + return admin.PasswordlessType_PASSWORDLESSTYPE_NOT_ALLOWED + } +} + +func passwordlessTypeToModel(passwordlessType admin.PasswordlessType) iam_model.PasswordlessType { + switch passwordlessType { + case admin.PasswordlessType_PASSWORDLESSTYPE_ALLOWED: + return iam_model.PasswordlessTypeAllowed + default: + return iam_model.PasswordlessTypeNotAllowed + } +} + func multiFactorResultFromModel(result *iam_model.MultiFactorsSearchResponse) *admin.MultiFactorsResult { converted := make([]admin.MultiFactorType, len(result.Result)) for i, mfaType := range result.Result { diff --git a/internal/api/grpc/admin/probes.go b/internal/api/grpc/admin/probes.go index 632471bf2d..0a67a7f967 100644 --- a/internal/api/grpc/admin/probes.go +++ b/internal/api/grpc/admin/probes.go @@ -2,7 +2,6 @@ package admin import ( "context" - "github.com/golang/protobuf/ptypes/empty" ) diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index ad0dba523f..e2d94e9d47 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -2,7 +2,6 @@ package auth import ( "context" - "github.com/golang/protobuf/ptypes/empty" "github.com/caos/zitadel/pkg/grpc/auth" @@ -54,7 +53,7 @@ func (s *Server) GetMyUserAddress(ctx context.Context, _ *empty.Empty) (*auth.Us } func (s *Server) GetMyMfas(ctx context.Context, _ *empty.Empty) (*auth.MultiFactors, error) { - mfas, err := s.repo.MyUserMfas(ctx) + mfas, err := s.repo.MyUserMFAs(ctx) if err != nil { return nil, err } @@ -144,7 +143,7 @@ func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Emp } func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpResponse, err error) { - otp, err := s.repo.AddMyMfaOTP(ctx) + otp, err := s.repo.AddMyMFAOTP(ctx) if err != nil { return nil, err } @@ -152,12 +151,42 @@ func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpR } func (s *Server) VerifyMfaOTP(ctx context.Context, request *auth.VerifyMfaOtp) (*empty.Empty, error) { - err := s.repo.VerifyMyMfaOTPSetup(ctx, request.Code) + err := s.repo.VerifyMyMFAOTPSetup(ctx, request.Code) return &empty.Empty{}, err } func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) { - s.repo.RemoveMyMfaOTP(ctx) + err = s.repo.RemoveMyMFAOTP(ctx) + return &empty.Empty{}, err +} + +func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { + u2f, err := s.repo.AddMyMFAU2F(ctx) + return verifyWebAuthNFromModel(u2f), err +} + +func (s *Server) VerifyMyMfaU2F(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { + err := s.repo.VerifyMyMFAU2FSetup(ctx, request.TokenName, request.PublicKeyCredential) + return &empty.Empty{}, err +} + +func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { + err := s.repo.RemoveMyMFAU2F(ctx, id.Id) + return &empty.Empty{}, err +} + +func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { + u2f, err := s.repo.AddMyPasswordless(ctx) + return verifyWebAuthNFromModel(u2f), err +} + +func (s *Server) VerifyMyPasswordless(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { + err := s.repo.VerifyMyPasswordlessSetup(ctx, request.TokenName, request.PublicKeyCredential) + return &empty.Empty{}, err +} + +func (s *Server) RemoveMyPasswordless(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { + err := s.repo.RemoveMyPasswordless(ctx, id.Id) return &empty.Empty{}, err } diff --git a/internal/api/grpc/auth/user_converter.go b/internal/api/grpc/auth/user_converter.go index 376904b7d8..59b9daa844 100644 --- a/internal/api/grpc/auth/user_converter.go +++ b/internal/api/grpc/auth/user_converter.go @@ -12,7 +12,7 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/pkg/grpc/auth" "github.com/caos/zitadel/pkg/grpc/message" @@ -358,11 +358,11 @@ func genderToModel(gender auth.Gender) usr_model.Gender { } } -func mfaStateFromModel(state usr_model.MfaState) auth.MFAState { +func mfaStateFromModel(state usr_model.MFAState) auth.MFAState { switch state { - case usr_model.MfaStateReady: + case usr_model.MFAStateReady: return auth.MFAState_MFASTATE_READY - case usr_model.MfaStateNotReady: + case usr_model.MFAStateNotReady: return auth.MFAState_MFASTATE_NOT_READY default: return auth.MFAState_MFASTATE_UNSPECIFIED @@ -379,17 +379,18 @@ func mfasFromModel(mfas []*usr_model.MultiFactor) []*auth.MultiFactor { func mfaFromModel(mfa *usr_model.MultiFactor) *auth.MultiFactor { return &auth.MultiFactor{ - State: mfaStateFromModel(mfa.State), - Type: mfaTypeFromModel(mfa.Type), + State: mfaStateFromModel(mfa.State), + Type: mfaTypeFromModel(mfa.Type), + Attribute: mfa.Attribute, } } -func mfaTypeFromModel(mfatype usr_model.MfaType) auth.MfaType { - switch mfatype { - case usr_model.MfaTypeOTP: +func mfaTypeFromModel(mfaType usr_model.MFAType) auth.MfaType { + switch mfaType { + case usr_model.MFATypeOTP: return auth.MfaType_MFATYPE_OTP - case usr_model.MfaTypeSMS: - return auth.MfaType_MFATYPE_SMS + case usr_model.MFATypeU2F: + return auth.MfaType_MFATYPE_U2F default: return auth.MfaType_MFATYPE_UNSPECIFIED } @@ -426,3 +427,11 @@ func userChangesToAPI(changes *usr_model.UserChanges) (_ []*auth.Change) { return result } + +func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNResponse { + return &auth.WebAuthNResponse{ + Id: u2f.WebAuthNTokenID, + PublicKey: u2f.PublicKey, + State: mfaStateFromModel(u2f.State), + } +} diff --git a/internal/api/grpc/management/login_policy_converter.go b/internal/api/grpc/management/login_policy_converter.go index f43691e247..6399233a98 100644 --- a/internal/api/grpc/management/login_policy_converter.go +++ b/internal/api/grpc/management/login_policy_converter.go @@ -13,6 +13,7 @@ func loginPolicyRequestToModel(policy *management.LoginPolicyRequest) *iam_model AllowExternalIdp: policy.AllowExternalIdp, AllowRegister: policy.AllowRegister, ForceMFA: policy.ForceMfa, + PasswordlessType: passwordlessTypeToModel(policy.PasswordlessType), } } @@ -30,6 +31,7 @@ func loginPolicyFromModel(policy *iam_model.LoginPolicy) *management.LoginPolicy CreationDate: creationDate, ChangeDate: changeDate, ForceMfa: policy.ForceMFA, + PasswordlessType: passwordlessTypeFromModel(policy.PasswordlessType), } } @@ -48,6 +50,7 @@ func loginPolicyViewFromModel(policy *iam_model.LoginPolicyView) *management.Log CreationDate: creationDate, ChangeDate: changeDate, ForceMfa: policy.ForceMFA, + PasswordlessType: passwordlessTypeFromModel(policy.PasswordlessType), } } @@ -215,3 +218,21 @@ func multiFactorTypeToModel(mfaType *management.MultiFactor) iam_model.MultiFact return iam_model.MultiFactorTypeUnspecified } } + +func passwordlessTypeFromModel(passwordlessType iam_model.PasswordlessType) management.PasswordlessType { + switch passwordlessType { + case iam_model.PasswordlessTypeAllowed: + return management.PasswordlessType_PASSWORDLESSTYPE_ALLOWED + default: + return management.PasswordlessType_PASSWORDLESSTYPE_NOT_ALLOWED + } +} + +func passwordlessTypeToModel(passwordlessType management.PasswordlessType) iam_model.PasswordlessType { + switch passwordlessType { + case management.PasswordlessType_PASSWORDLESSTYPE_ALLOWED: + return iam_model.PasswordlessTypeAllowed + default: + return iam_model.PasswordlessTypeNotAllowed + } +} diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index bf7f0abbd8..031503118c 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -214,7 +214,7 @@ func (s *Server) RemoveExternalIDP(ctx context.Context, request *management.Exte } func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*management.UserMultiFactors, error) { - mfas, err := s.user.UserMfas(ctx, userID.Id) + mfas, err := s.user.UserMFAs(ctx, userID.Id) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index c1e38301d7..5f10e61a72 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -572,22 +572,22 @@ func genderToModel(gender management.Gender) usr_model.Gender { } } -func mfaTypeFromModel(mfatype usr_model.MfaType) management.MfaType { +func mfaTypeFromModel(mfatype usr_model.MFAType) management.MfaType { switch mfatype { - case usr_model.MfaTypeOTP: + case usr_model.MFATypeOTP: return management.MfaType_MFATYPE_OTP - case usr_model.MfaTypeSMS: - return management.MfaType_MFATYPE_SMS + case usr_model.MFATypeU2F: + return management.MfaType_MFATYPE_U2F default: return management.MfaType_MFATYPE_UNSPECIFIED } } -func mfaStateFromModel(state usr_model.MfaState) management.MFAState { +func mfaStateFromModel(state usr_model.MFAState) management.MFAState { switch state { - case usr_model.MfaStateReady: + case usr_model.MFAStateReady: return management.MFAState_MFASTATE_READY - case usr_model.MfaStateNotReady: + case usr_model.MFAStateNotReady: return management.MFAState_MFASTATE_NOT_READY default: return management.MFAState_MFASTATE_UNSPECIFIED diff --git a/internal/api/grpc/server/gateway.go b/internal/api/grpc/server/gateway.go index 1833e905ff..675b0b41a9 100644 --- a/internal/api/grpc/server/gateway.go +++ b/internal/api/grpc/server/gateway.go @@ -14,7 +14,7 @@ import ( client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware" http_util "github.com/caos/zitadel/internal/api/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( @@ -129,7 +129,8 @@ func createDialOptions(g Gateway) []grpc.DialOption { } func addInterceptors(handler http.Handler, g Gateway) http.Handler { - handler = http_mw.DefaultTraceHandler(handler) + handler = http_mw.DefaultMetricsHandler(handler) + handler = http_mw.DefaultTelemetryHandler(handler) handler = http_mw.NoCacheInterceptor(handler) if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok { handler = interceptor.GatewayHTTPInterceptor(handler) diff --git a/internal/api/grpc/server/middleware/auth_interceptor.go b/internal/api/grpc/server/middleware/auth_interceptor.go index e46f661098..347e0b18f9 100644 --- a/internal/api/grpc/server/middleware/auth_interceptor.go +++ b/internal/api/grpc/server/middleware/auth_interceptor.go @@ -10,7 +10,7 @@ import ( "github.com/caos/zitadel/internal/api/authz" grpc_util "github.com/caos/zitadel/internal/api/grpc" "github.com/caos/zitadel/internal/api/http" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor { diff --git a/internal/api/grpc/server/middleware/metrics_interceptor.go b/internal/api/grpc/server/middleware/metrics_interceptor.go new file mode 100644 index 0000000000..4dcf6cc8a8 --- /dev/null +++ b/internal/api/grpc/server/middleware/metrics_interceptor.go @@ -0,0 +1,86 @@ +package middleware + +import ( + "context" + "strings" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + "google.golang.org/grpc/status" + + _ "github.com/caos/zitadel/internal/statik" + "github.com/caos/zitadel/internal/telemetry/metrics" +) + +const ( + GrpcMethod = "grpc_method" + ReturnCode = "return_code" + GrpcRequestCounter = "grpc.server.request_counter" + GrpcRequestCounterDescription = "Grpc request counter" + TotalGrpcRequestCounter = "grpc.server.total_request_counter" + TotalGrpcRequestCounterDescription = "Total grpc request counter" + GrpcStatusCodeCounter = "grpc.server.grpc_status_code" + GrpcStatusCodeCounterDescription = "Grpc status code counter" +) + +func MetricsHandler(metricTypes []metrics.MetricType, ignoredMethodSuffixes ...string) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return RegisterMetrics(ctx, req, info, handler, metricTypes, ignoredMethodSuffixes...) + } +} + +func RegisterMetrics(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, metricTypes []metrics.MetricType, ignoredMethodSuffixes ...string) (_ interface{}, err error) { + if len(metricTypes) == 0 { + return handler(ctx, req) + } + + for _, ignore := range ignoredMethodSuffixes { + if strings.HasSuffix(info.FullMethod, ignore) { + return handler(ctx, req) + } + } + + resp, err := handler(ctx, req) + if containsMetricsMethod(metrics.MetricTypeRequestCount, metricTypes) { + RegisterGrpcRequestCounter(ctx, info) + } + if containsMetricsMethod(metrics.MetricTypeTotalCount, metricTypes) { + RegisterGrpcTotalRequestCounter(ctx) + } + if containsMetricsMethod(metrics.MetricTypeStatusCode, metricTypes) { + RegisterGrpcRequestCodeCounter(ctx, info, err) + } + return resp, err +} + +func RegisterGrpcRequestCounter(ctx context.Context, info *grpc.UnaryServerInfo) { + var labels = map[string]interface{}{ + GrpcMethod: info.FullMethod, + } + metrics.RegisterCounter(GrpcRequestCounter, GrpcRequestCounterDescription) + metrics.AddCount(ctx, GrpcRequestCounter, 1, labels) +} + +func RegisterGrpcTotalRequestCounter(ctx context.Context) { + metrics.RegisterCounter(TotalGrpcRequestCounter, TotalGrpcRequestCounterDescription) + metrics.AddCount(ctx, TotalGrpcRequestCounter, 1, nil) +} + +func RegisterGrpcRequestCodeCounter(ctx context.Context, info *grpc.UnaryServerInfo, err error) { + statusCode := status.Code(err) + var labels = map[string]interface{}{ + GrpcMethod: info.FullMethod, + ReturnCode: runtime.HTTPStatusFromCode(statusCode), + } + metrics.RegisterCounter(GrpcStatusCodeCounter, GrpcStatusCodeCounterDescription) + metrics.AddCount(ctx, GrpcStatusCodeCounter, 1, labels) +} + +func containsMetricsMethod(metricType metrics.MetricType, metricTypes []metrics.MetricType) bool { + for _, m := range metricTypes { + if m == metricType { + return true + } + } + return false +} diff --git a/internal/api/grpc/server/probes.go b/internal/api/grpc/server/probes.go index 434c07a1bf..e794a44655 100644 --- a/internal/api/grpc/server/probes.go +++ b/internal/api/grpc/server/probes.go @@ -9,7 +9,7 @@ import ( "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/proto" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type ValidationFunction func(ctx context.Context) error diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index e5816ec7d9..ad8bf1699b 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -2,6 +2,8 @@ package server import ( "context" + grpc_api "github.com/caos/zitadel/internal/api/grpc" + "github.com/caos/zitadel/internal/telemetry/metrics" "github.com/caos/logging" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -11,7 +13,7 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/server/middleware" "github.com/caos/zitadel/internal/api/http" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( @@ -27,10 +29,12 @@ type Server interface { } func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang language.Tag) *grpc.Server { + metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode} return grpc.NewServer( grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( middleware.DefaultTracingServer(), + middleware.MetricsHandler(metricTypes, grpc_api.Probes...), middleware.ErrorHandler(), middleware.AuthorizationInterceptor(verifier, authConfig), middleware.TranslationHandler(lang), diff --git a/internal/api/http/middleware/metrics_interceptor.go b/internal/api/http/middleware/metrics_interceptor.go new file mode 100644 index 0000000000..a47f5b32ab --- /dev/null +++ b/internal/api/http/middleware/metrics_interceptor.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "github.com/caos/zitadel/internal/telemetry/metrics" + "net/http" + + http_utils "github.com/caos/zitadel/internal/api/http" +) + +func DefaultMetricsHandler(handler http.Handler) http.Handler { + metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount} + return MetricsHandler(metricTypes, http_utils.Probes...)(handler) +} + +func MetricsHandler(metricTypes []metrics.MetricType, ignoredMethods ...string) func(http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return metrics.NewMetricsHandler(handler, metricTypes, ignoredMethods...) + } +} diff --git a/internal/api/http/middleware/telemetry_interceptor.go b/internal/api/http/middleware/telemetry_interceptor.go new file mode 100644 index 0000000000..bc447c0a1d --- /dev/null +++ b/internal/api/http/middleware/telemetry_interceptor.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/caos/zitadel/internal/telemetry" + "net/http" + + http_utils "github.com/caos/zitadel/internal/api/http" +) + +func DefaultTelemetryHandler(handler http.Handler) http.Handler { + return TelemetryHandler(http_utils.Probes...)(handler) +} + +func TelemetryHandler(ignoredMethods ...string) func(http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return telemetry.TelemetryHandler(handler, ignoredMethods...) + } +} diff --git a/internal/api/http/middleware/trace_interceptor.go b/internal/api/http/middleware/trace_interceptor.go deleted file mode 100644 index af800c95ff..0000000000 --- a/internal/api/http/middleware/trace_interceptor.go +++ /dev/null @@ -1,18 +0,0 @@ -package middleware - -import ( - "net/http" - - http_utils "github.com/caos/zitadel/internal/api/http" - "github.com/caos/zitadel/internal/tracing" -) - -func DefaultTraceHandler(handler http.Handler) http.Handler { - return TraceHandler(http_utils.Probes...)(handler) -} - -func TraceHandler(ignoredMethods ...string) func(http.Handler) http.Handler { - return func(handler http.Handler) http.Handler { - return tracing.TraceHandler(handler, ignoredMethods...) - } -} diff --git a/internal/api/http/server.go b/internal/api/http/server.go index f663b2acd0..489fcc7ec9 100644 --- a/internal/api/http/server.go +++ b/internal/api/http/server.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/caos/logging" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) func Serve(ctx context.Context, handler http.Handler, port, servername string) { diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index 07e2f497b0..e1c57c64b5 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -13,7 +13,7 @@ import ( "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/errors" proj_model "github.com/caos/zitadel/internal/project/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" grant_model "github.com/caos/zitadel/internal/usergrant/model" ) diff --git a/internal/api/oidc/auth_request_converter.go b/internal/api/oidc/auth_request_converter.go index 06dba25dfe..5dd4dc15b6 100644 --- a/internal/api/oidc/auth_request_converter.go +++ b/internal/api/oidc/auth_request_converter.go @@ -15,9 +15,10 @@ import ( ) const ( - amrPassword = "password" - amrMFA = "mfa" - amrOTP = "otp" + amrPassword = "password" + amrMFA = "mfa" + amrOTP = "otp" + amrUserPresence = "user" ) type AuthRequest struct { @@ -38,11 +39,11 @@ func (a *AuthRequest) GetAMR() []string { if a.PasswordVerified { amr = append(amr, amrPassword) } - if len(a.MfasVerified) > 0 { + if len(a.MFAsVerified) > 0 { amr = append(amr, amrMFA) - for _, mfa := range a.MfasVerified { - if amrMfa := AMRFromMFAType(mfa); amrMfa != "" { - amr = append(amr, amrMfa) + for _, mfa := range a.MFAsVerified { + if amrMFA := AMRFromMFAType(mfa); amrMFA != "" { + amr = append(amr, amrMFA) } } } @@ -247,6 +248,9 @@ func AMRFromMFAType(mfaType model.MFAType) string { switch mfaType { case model.MFATypeOTP: return amrOTP + case model.MFATypeU2F, + model.MFATypeU2FUserVerification: + return amrUserPresence default: return "" } diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index a59176b66a..760c8a007d 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -16,7 +16,7 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" proj_model "github.com/caos/zitadel/internal/project/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" user_model "github.com/caos/zitadel/internal/user/model" grant_model "github.com/caos/zitadel/internal/usergrant/model" ) diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index d3ba142264..f5a11012bf 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -2,6 +2,7 @@ package oidc import ( "context" + "github.com/caos/zitadel/internal/telemetry/metrics" "time" "github.com/caos/logging" @@ -12,7 +13,7 @@ import ( "github.com/caos/zitadel/internal/auth/repository" "github.com/caos/zitadel/internal/config/types" "github.com/caos/zitadel/internal/id" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type OPHandlerConfig struct { @@ -55,12 +56,14 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re cookieHandler, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode) logging.Log("OIDC-sd4fd").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot user agent handler") config.OPConfig.CodeMethodS256 = true + metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} provider, err := op.NewOpenIDProvider( ctx, config.OPConfig, newStorage(config.StorageConfig, repo), op.WithHttpInterceptors( - middleware.TraceHandler(), + middleware.MetricsHandler(metricTypes), + middleware.TelemetryHandler(), middleware.NoCacheInterceptor, cookieHandler, http_utils.CopyHeadersToContext, diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index c3301296a6..3aaab44596 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -2,10 +2,9 @@ package repository import ( "context" + "github.com/caos/zitadel/internal/auth_request/model" org_model "github.com/caos/zitadel/internal/org/model" user_model "github.com/caos/zitadel/internal/user/model" - - "github.com/caos/zitadel/internal/auth_request/model" ) type AuthRequestRepository interface { @@ -15,14 +14,22 @@ type AuthRequestRepository interface { AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error) SaveAuthCode(ctx context.Context, id, code, userAgentID string) error DeleteAuthRequest(ctx context.Context, id string) error + CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *model.ExternalUser, info *model.BrowserInfo) error SelectUser(ctx context.Context, id, userID, userAgentID string) error SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error - VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error + + VerifyMFAOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error + BeginMFAU2FLogin(ctx context.Context, userID, authRequestID, userAgentID string) (*user_model.WebAuthNLogin, error) + VerifyMFAU2F(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) error + BeginPasswordlessLogin(ctx context.Context, userID, authRequestID, userAgentID string) (*user_model.WebAuthNLogin, error) + VerifyPasswordless(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) error + LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) error AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) error ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error + GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) } diff --git a/internal/auth/repository/eventsourcing/eventstore/application.go b/internal/auth/repository/eventsourcing/eventstore/application.go index 5d5bc76102..95540846fb 100644 --- a/internal/auth/repository/eventsourcing/eventstore/application.go +++ b/internal/auth/repository/eventsourcing/eventstore/application.go @@ -7,6 +7,7 @@ import ( "github.com/caos/zitadel/internal/project/model" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type ApplicationRepo struct { @@ -22,7 +23,10 @@ func (a *ApplicationRepo) ApplicationByClientID(ctx context.Context, clientID st return proj_view_model.ApplicationViewToModel(app), nil } -func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error { +func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + app, err := a.View.ApplicationByClientID(ctx, clientID) if err != nil { return err diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 22e1d73d39..7c7a7bb8ea 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -21,7 +21,7 @@ import ( org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" project_view_model "github.com/caos/zitadel/internal/project/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" user_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" @@ -47,7 +47,7 @@ type AuthRequestRepo struct { PasswordCheckLifeTime time.Duration ExternalLoginCheckLifeTime time.Duration - MfaInitSkippedLifeTime time.Duration + MFAInitSkippedLifeTime time.Duration SecondFactorCheckLifeTime time.Duration MultiFactorCheckLifeTime time.Duration @@ -245,27 +245,62 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - request, err := repo.getAuthRequest(ctx, id, userAgentID) + request, err := repo.getAuthRequestEnsureUser(ctx, id, userAgentID, userID) if err != nil { return err } - if request.UserID != userID { - return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "Errors.User.NotMatchingUserID") - } return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info)) } -func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, userID, code, userAgentID string, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, userID, code, userAgentID string, info *model.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - request, err := repo.getAuthRequest(ctx, authRequestID, userAgentID) + request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) if err != nil { return err } - if request.UserID != userID { - return errors.ThrowPreconditionFailed(nil, "EVENT-ADJ26", "Errors.User.NotMatchingUserID") + return repo.UserEvents.CheckMFAOTP(ctx, userID, code, request.WithCurrentInfo(info)) +} + +func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, authRequestID, userAgentID string) (login *user_model.WebAuthNLogin, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) + if err != nil { + return nil, err } - return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info)) + return repo.UserEvents.BeginU2FLogin(ctx, userID, request) +} + +func (repo *AuthRequestRepo) VerifyMFAU2F(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) + if err != nil { + return err + } + return repo.UserEvents.VerifyMFAU2F(ctx, userID, credentialData, request) +} + +func (repo *AuthRequestRepo) BeginPasswordlessLogin(ctx context.Context, userID, authRequestID, userAgentID string) (login *user_model.WebAuthNLogin, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) + if err != nil { + return nil, err + } + return repo.UserEvents.BeginPasswordlessLogin(ctx, userID, request) +} + +func (repo *AuthRequestRepo) VerifyPasswordless(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) + if err != nil { + return err + } + return repo.UserEvents.VerifyPasswordless(ctx, userID, credentialData, request) } func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) (err error) { @@ -365,6 +400,17 @@ func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, us return request, nil } +func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authRequestID, userAgentID, userID string) (*model.AuthRequest, error) { + request, err := repo.getAuthRequest(ctx, authRequestID, userAgentID) + if err != nil { + return nil, err + } + if request.UserID != userID { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-GBH32", "Errors.User.NotMatchingUserID") + } + return request, nil +} + func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) { request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id) if err != nil { @@ -545,27 +591,19 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR return nil, err } - if (request.SelectedIDPConfigID != "" || userSession.SelectedIDPConfigID != "") && (request.LinkingUsers == nil || len(request.LinkingUsers) == 0) { - if !checkVerificationTime(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime) { - selectedIDPConfigID := request.SelectedIDPConfigID - if selectedIDPConfigID == "" { - selectedIDPConfigID = userSession.SelectedIDPConfigID - } - return append(steps, &model.ExternalLoginStep{SelectedIDPConfigID: selectedIDPConfigID}), nil + isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "" + if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTime(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime) { + selectedIDPConfigID := request.SelectedIDPConfigID + if selectedIDPConfigID == "" { + selectedIDPConfigID = userSession.SelectedIDPConfigID } - } else if (request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "") || (request.SelectedIDPConfigID != "" && request.LinkingUsers != nil && len(request.LinkingUsers) > 0) { - if user.InitRequired { - return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil + return append(steps, &model.ExternalLoginStep{SelectedIDPConfigID: selectedIDPConfigID}), nil + } + if isInternalLogin || (!isInternalLogin && len(request.LinkingUsers) > 0) { + step := repo.firstFactorChecked(request, user, userSession) + if step != nil { + return append(steps, step), nil } - if !user.PasswordSet { - return append(steps, &model.InitPasswordStep{}), nil - } - - if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) { - return append(steps, &model.PasswordStep{}), nil - } - request.PasswordVerified = true - request.AuthTime = userSession.PasswordVerification } step, ok, err := repo.mfaChecked(userSession, request, user) @@ -624,21 +662,46 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) ( return users, nil } +func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user *user_model.UserView, userSession *user_model.UserSessionView) model.NextStep { + if user.InitRequired { + return &model.InitUserStep{PasswordSet: user.PasswordSet} + } + + if user.IsPasswordlessReady() { + if !checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) { + return &model.PasswordlessStep{} + } + request.AuthTime = userSession.PasswordlessVerification + return nil + } + + if !user.PasswordSet { + return &model.InitPasswordStep{} + } + + if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) { + return &model.PasswordStep{} + } + request.PasswordVerified = true + request.AuthTime = userSession.PasswordVerification + return nil +} + func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool, error) { - mfaLevel := request.MfaLevel() - allowedProviders, required := user.MfaTypesAllowed(mfaLevel, request.LoginPolicy) - promptRequired := (user.MfaMaxSetUp < mfaLevel) || (len(allowedProviders) == 0 && required) + mfaLevel := request.MFALevel() + allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy) + promptRequired := (user.MFAMaxSetUp < mfaLevel) || (len(allowedProviders) == 0 && required) if promptRequired || !repo.mfaSkippedOrSetUp(user) { - types := user.MfaTypesSetupPossible(mfaLevel, request.LoginPolicy) + types := user.MFATypesSetupPossible(mfaLevel, request.LoginPolicy) if promptRequired && len(types) == 0 { return nil, false, errors.ThrowPreconditionFailed(nil, "LOGIN-5Hm8s", "Errors.Login.LoginPolicy.MFA.ForceAndNotConfigured") } if len(types) == 0 { return nil, true, nil } - return &model.MfaPromptStep{ + return &model.MFAPromptStep{ Required: promptRequired, - MfaProviders: types, + MFAProviders: types, }, false, nil } switch mfaLevel { @@ -651,28 +714,28 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, fallthrough case model.MFALevelSecondFactor: if checkVerificationTime(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime) { - request.MfasVerified = append(request.MfasVerified, userSession.SecondFactorVerificationType) + request.MFAsVerified = append(request.MFAsVerified, userSession.SecondFactorVerificationType) request.AuthTime = userSession.SecondFactorVerification return nil, true, nil } fallthrough case model.MFALevelMultiFactor: if checkVerificationTime(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime) { - request.MfasVerified = append(request.MfasVerified, userSession.MultiFactorVerificationType) + request.MFAsVerified = append(request.MFAsVerified, userSession.MultiFactorVerificationType) request.AuthTime = userSession.MultiFactorVerification return nil, true, nil } } - return &model.MfaVerificationStep{ - MfaProviders: allowedProviders, + return &model.MFAVerificationStep{ + MFAProviders: allowedProviders, }, false, nil } func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool { - if user.MfaMaxSetUp > model.MFALevelNotSetUp { + if user.MFAMaxSetUp > model.MFALevelNotSetUp { return true } - return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime) + return checkVerificationTime(user.MFAInitSkipped, repo.MFAInitSkippedLifeTime) } func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, error) { @@ -745,7 +808,11 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve es_model.HumanExternalLoginCheckSucceeded, es_model.HumanMFAOTPCheckSucceeded, es_model.HumanMFAOTPCheckFailed, - es_model.HumanSignedOut: + es_model.HumanSignedOut, + es_model.HumanPasswordlessTokenCheckSucceeded, + es_model.HumanPasswordlessTokenCheckFailed, + es_model.HumanMFAU2FTokenCheckSucceeded, + es_model.HumanMFAU2FTokenCheckFailed: eventData, err := user_view_model.UserSessionFromEvent(event) if err != nil { logging.Log("EVENT-sdgT3").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error getting event data") diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index e12ea165e5..345bbfc727 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -48,8 +48,10 @@ func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*user_view_mod type mockViewUserSession struct { ExternalLoginVerification time.Time + PasswordlessVerification time.Time PasswordVerification time.Time SecondFactorVerification time.Time + MultiFactorVerification time.Time Users []mockUser } @@ -61,8 +63,10 @@ type mockUser struct { func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) { return &user_view_model.UserSessionView{ ExternalLoginVerification: m.ExternalLoginVerification, + PasswordlessVerification: m.PasswordlessVerification, PasswordVerification: m.PasswordVerification, SecondFactorVerification: m.SecondFactorVerification, + MultiFactorVerification: m.MultiFactorVerification, }, nil } @@ -115,8 +119,9 @@ type mockViewUser struct { PasswordChangeRequired bool IsEmailVerified bool OTPState int32 - MfaMaxSetUp int32 - MfaInitSkipped time.Time + MFAMaxSetUp int32 + MFAInitSkipped time.Time + PasswordlessTokens user_view_model.WebAuthNTokens } type mockLoginPolicy struct { @@ -138,8 +143,9 @@ func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) { PasswordChangeRequired: m.PasswordChangeRequired, IsEmailVerified: m.IsEmailVerified, OTPState: m.OTPState, - MfaMaxSetUp: m.MfaMaxSetUp, - MfaInitSkipped: m.MfaInitSkipped, + MFAMaxSetUp: m.MFAMaxSetUp, + MFAInitSkipped: m.MFAInitSkipped, + PasswordlessTokens: m.PasswordlessTokens, }, }, nil } @@ -200,7 +206,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { loginPolicyProvider loginPolicyViewProvider PasswordCheckLifeTime time.Duration ExternalLoginCheckLifeTime time.Duration - MfaInitSkippedLifeTime time.Duration + MFAInitSkippedLifeTime time.Duration SecondFactorCheckLifeTime time.Duration MultiFactorCheckLifeTime time.Duration } @@ -413,6 +419,49 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }}, nil, }, + { + "passwordless not verified, passwordless check step", + fields{ + userSessionViewProvider: &mockViewUserSession{}, + userViewProvider: &mockViewUser{ + PasswordSet: true, + PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + MultiFactorCheckLifeTime: 10 * time.Hour, + }, + args{&model.AuthRequest{UserID: "UserID"}, false}, + []model.NextStep{&model.PasswordlessStep{}}, + nil, + }, + { + "passwordless verified, email not verified, email verification step", + fields{ + userSessionViewProvider: &mockViewUserSession{ + PasswordlessVerification: time.Now().Add(-5 * time.Minute), + MultiFactorVerification: time.Now().Add(-5 * time.Minute), + }, + userViewProvider: &mockViewUser{ + PasswordSet: true, + PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, + PasswordChangeRequired: false, + IsEmailVerified: false, + MFAMaxSetUp: int32(model.MFALevelMultiFactor), + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + MultiFactorCheckLifeTime: 10 * time.Hour, + }, + args{&model.AuthRequest{ + UserID: "UserID", + LoginPolicy: &iam_model.LoginPolicyView{ + MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN}, + }, + }, false}, + []model.NextStep{&model.VerifyEMailStep{}}, + nil, + }, { "password not set, init password step", fields{ @@ -433,7 +482,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userViewProvider: &mockViewUser{ IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -452,7 +501,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userViewProvider: &mockViewUser{ IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -499,7 +548,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -525,8 +574,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userViewProvider: &mockViewUser{ PasswordSet: true, - OTPState: int32(user_model.MfaStateReady), - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + OTPState: int32(user_model.MFAStateReady), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -540,8 +589,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.MfaVerificationStep{ - MfaProviders: []model.MFAType{model.MFATypeOTP}, + []model.NextStep{&model.MFAVerificationStep{ + MFAProviders: []model.MFAType{model.MFATypeOTP}, }}, nil, }, @@ -554,8 +603,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userViewProvider: &mockViewUser{ PasswordSet: true, - OTPState: int32(user_model.MfaStateReady), - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + OTPState: int32(user_model.MFAStateReady), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -571,8 +620,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.MfaVerificationStep{ - MfaProviders: []model.MFAType{model.MFATypeOTP}, + []model.NextStep{&model.MFAVerificationStep{ + MFAProviders: []model.MFAType{model.MFATypeOTP}, }}, nil, }, @@ -587,7 +636,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, PasswordChangeRequired: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -613,7 +662,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userViewProvider: &mockViewUser{ PasswordSet: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -639,7 +688,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, PasswordChangeRequired: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -665,7 +714,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -693,7 +742,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -722,7 +771,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -754,7 +803,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -785,7 +834,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -810,7 +859,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordSet: true, IsEmailVerified: true, - MfaMaxSetUp: int32(model.MFALevelSecondFactor), + MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, @@ -844,7 +893,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { LoginPolicyViewProvider: tt.fields.loginPolicyProvider, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime, - MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, + MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime, MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime, } @@ -860,7 +909,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { func TestAuthRequestRepo_mfaChecked(t *testing.T) { type fields struct { - MfaInitSkippedLifeTime time.Duration + MFAInitSkippedLifeTime time.Duration SecondFactorCheckLifeTime time.Duration MultiFactorCheckLifeTime time.Duration } @@ -884,7 +933,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { // args{ // request: &model.AuthRequest{PossibleLOAs: []model.LevelOfAssurance{}}, // user: &user_model.UserView{ - // OTPState: user_model.MfaStateReady, + // OTPState: user_model.MFAStateReady, // }, // }, // false, @@ -892,7 +941,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { { "not set up, forced by policy, no mfas configured, error", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ request: &model.AuthRequest{ @@ -902,7 +951,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelNotSetUp, + MFAMaxSetUp: model.MFALevelNotSetUp, }, }, }, @@ -913,7 +962,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { { "not set up, no mfas configured, no prompt and true", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ request: &model.AuthRequest{ @@ -921,7 +970,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelNotSetUp, + MFAMaxSetUp: model.MFALevelNotSetUp, }, }, }, @@ -932,7 +981,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { { "not set up, prompt and false", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ request: &model.AuthRequest{ @@ -942,12 +991,12 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelNotSetUp, + MFAMaxSetUp: model.MFALevelNotSetUp, }, }, }, - &model.MfaPromptStep{ - MfaProviders: []model.MFAType{ + &model.MFAPromptStep{ + MFAProviders: []model.MFAType{ model.MFATypeOTP, }, }, @@ -957,7 +1006,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { { "not set up, forced by org, true", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ request: &model.AuthRequest{ @@ -968,13 +1017,13 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelNotSetUp, + MFAMaxSetUp: model.MFALevelNotSetUp, }, }, }, - &model.MfaPromptStep{ + &model.MFAPromptStep{ Required: true, - MfaProviders: []model.MFAType{ + MFAProviders: []model.MFAType{ model.MFATypeOTP, }, }, @@ -984,7 +1033,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { { "not set up and skipped, true", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ request: &model.AuthRequest{ @@ -992,8 +1041,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelNotSetUp, - MfaInitSkipped: time.Now().UTC(), + MFAMaxSetUp: model.MFALevelNotSetUp, + MFAInitSkipped: time.Now().UTC(), }, }, }, @@ -1014,8 +1063,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelSecondFactor, - OTPState: user_model.MfaStateReady, + MFAMaxSetUp: model.MFALevelSecondFactor, + OTPState: user_model.MFAStateReady, }, }, userSession: &user_model.UserSessionView{SecondFactorVerification: time.Now().UTC().Add(-5 * time.Hour)}, @@ -1037,15 +1086,15 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelSecondFactor, - OTPState: user_model.MfaStateReady, + MFAMaxSetUp: model.MFALevelSecondFactor, + OTPState: user_model.MFAStateReady, }, }, userSession: &user_model.UserSessionView{}, }, - &model.MfaVerificationStep{ - MfaProviders: []model.MFAType{model.MFATypeOTP}, + &model.MFAVerificationStep{ + MFAProviders: []model.MFAType{model.MFATypeOTP}, }, false, nil, @@ -1054,7 +1103,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := &AuthRequestRepo{ - MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, + MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime, MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime, } @@ -1073,7 +1122,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { type fields struct { - MfaInitSkippedLifeTime time.Duration + MFAInitSkippedLifeTime time.Duration } type args struct { user *user_model.UserView @@ -1090,7 +1139,7 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { args{ &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: model.MFALevelSecondFactor, + MFAMaxSetUp: model.MFALevelSecondFactor, }, }, }, @@ -1099,13 +1148,13 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { { "mfa skipped active, true", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: -1, - MfaInitSkipped: time.Now().UTC().Add(-10 * time.Hour), + MFAMaxSetUp: -1, + MFAInitSkipped: time.Now().UTC().Add(-10 * time.Hour), }, }, }, @@ -1114,13 +1163,13 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { { "mfa skipped inactive, false", fields{ - MfaInitSkippedLifeTime: 30 * 24 * time.Hour, + MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ &user_model.UserView{ HumanView: &user_model.HumanView{ - MfaMaxSetUp: -1, - MfaInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour), + MFAMaxSetUp: -1, + MFAInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour), }, }, }, @@ -1130,7 +1179,7 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := &AuthRequestRepo{ - MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, + MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, } if got := repo.mfaSkippedOrSetUp(tt.args.user); got != tt.want { t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want) diff --git a/internal/auth/repository/eventsourcing/eventstore/org.go b/internal/auth/repository/eventsourcing/eventstore/org.go index f521a00a77..c5f3ecced0 100644 --- a/internal/auth/repository/eventsourcing/eventstore/org.go +++ b/internal/auth/repository/eventsourcing/eventstore/org.go @@ -9,7 +9,7 @@ import ( "github.com/caos/zitadel/internal/errors" iam_model "github.com/caos/zitadel/internal/iam/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" auth_model "github.com/caos/zitadel/internal/auth/model" auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" @@ -50,7 +50,7 @@ func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.Or } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/token.go b/internal/auth/repository/eventsourcing/eventstore/token.go index 0d7d378bc8..ec3e81aa7b 100644 --- a/internal/auth/repository/eventsourcing/eventstore/token.go +++ b/internal/auth/repository/eventsourcing/eventstore/token.go @@ -3,17 +3,18 @@ package eventstore import ( "context" "strings" - "time" "github.com/caos/logging" - "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" auth_req_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" "github.com/caos/zitadel/internal/user/repository/view/model" + "time" + + "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" ) type TokenRepo struct { diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 9541625986..af6d62b2b1 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -16,7 +16,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/sdk" org_model "github.com/caos/zitadel/internal/org/model" org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" @@ -124,7 +124,7 @@ func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.E } if seqErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -253,18 +253,22 @@ func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new strin return err } -func (repo *UserRepo) MyUserMfas(ctx context.Context) ([]*model.MultiFactor, error) { +func (repo *UserRepo) MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { return nil, err } - if user.OTPState == model.MfaStateUnspecified { - return []*model.MultiFactor{}, nil + mfas := make([]*model.MultiFactor, 0) + if user.OTPState != model.MFAStateUnspecified { + mfas = append(mfas, &model.MultiFactor{Type: model.MFATypeOTP, State: user.OTPState}) } - return []*model.MultiFactor{{Type: model.MfaTypeOTP, State: user.OTPState}}, nil + for _, u2f := range user.U2FTokens { + mfas = append(mfas, &model.MultiFactor{Type: model.MFATypeU2F, State: u2f.State, Attribute: u2f.Name}) + } + return mfas, nil } -func (repo *UserRepo) AddMfaOTP(ctx context.Context, userID string) (*model.OTP, error) { +func (repo *UserRepo) AddMFAOTP(ctx context.Context, userID string) (*model.OTP, error) { accountName := "" user, err := repo.UserByID(ctx, userID) if err != nil { @@ -275,7 +279,7 @@ func (repo *UserRepo) AddMfaOTP(ctx context.Context, userID string) (*model.OTP, return repo.UserEvents.AddOTP(ctx, userID, accountName) } -func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) { +func (repo *UserRepo) AddMyMFAOTP(ctx context.Context) (*model.OTP, error) { accountName := "" user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -286,18 +290,66 @@ func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) { return repo.UserEvents.AddOTP(ctx, authz.GetCtxData(ctx).UserID, accountName) } -func (repo *UserRepo) VerifyMfaOTPSetup(ctx context.Context, userID, code string) error { - return repo.UserEvents.CheckMfaOTPSetup(ctx, userID, code) +func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code string) error { + return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code) } -func (repo *UserRepo) VerifyMyMfaOTPSetup(ctx context.Context, code string) error { - return repo.UserEvents.CheckMfaOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code) +func (repo *UserRepo) VerifyMyMFAOTPSetup(ctx context.Context, code string) error { + return repo.UserEvents.CheckMFAOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code) } -func (repo *UserRepo) RemoveMyMfaOTP(ctx context.Context) error { +func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error { return repo.UserEvents.RemoveOTP(ctx, authz.GetCtxData(ctx).UserID) } +func (repo *UserRepo) AddMFAU2F(ctx context.Context, userID string) (*model.WebAuthNToken, error) { + return repo.UserEvents.AddU2F(ctx, userID) +} + +func (repo *UserRepo) AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) { + return repo.UserEvents.AddU2F(ctx, authz.GetCtxData(ctx).UserID) +} + +func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { + return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, credentialData) +} + +func (repo *UserRepo) VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, credentialData []byte) error { + return repo.UserEvents.VerifyU2FSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, credentialData) +} + +func (repo *UserRepo) RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error { + return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID) +} + +func (repo *UserRepo) RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error { + return repo.UserEvents.RemoveU2FToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) +} + +func (repo *UserRepo) AddPasswordless(ctx context.Context, userID string) (*model.WebAuthNToken, error) { + return repo.UserEvents.AddPasswordless(ctx, userID) +} + +func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) { + return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID) +} + +func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { + return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, credentialData) +} + +func (repo *UserRepo) VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, credentialData []byte) error { + return repo.UserEvents.VerifyPasswordlessSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, credentialData) +} + +func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error { + return repo.UserEvents.RemovePasswordlessToken(ctx, userID, webAuthNTokenID) +} + +func (repo *UserRepo) RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error { + return repo.UserEvents.RemovePasswordlessToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) +} + func (repo *UserRepo) ChangeMyUsername(ctx context.Context, username string) error { ctxData := authz.GetCtxData(ctx) orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(ctxData.OrgID) @@ -327,8 +379,8 @@ func (repo *UserRepo) VerifyInitCode(ctx context.Context, userID, code, password return repo.UserEvents.VerifyInitCode(ctx, pwPolicyView, userID, code, password) } -func (repo *UserRepo) SkipMfaInit(ctx context.Context, userID string) error { - return repo.UserEvents.SkipMfaInit(ctx, userID) +func (repo *UserRepo) SkipMFAInit(ctx context.Context, userID string) error { + return repo.UserEvents.SkipMFAInit(ctx, userID) } func (repo *UserRepo) RequestPasswordReset(ctx context.Context, loginname string) error { diff --git a/internal/auth/repository/eventsourcing/eventstore/user_grant.go b/internal/auth/repository/eventsourcing/eventstore/user_grant.go index 038d0f0a53..cba39e4314 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/auth/repository/eventsourcing/eventstore/user_grant.go @@ -12,7 +12,7 @@ import ( global_model "github.com/caos/zitadel/internal/model" org_model "github.com/caos/zitadel/internal/org/model" org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" user_model "github.com/caos/zitadel/internal/user/model" user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" grant_model "github.com/caos/zitadel/internal/usergrant/model" @@ -44,7 +44,7 @@ func (repo *UserGrantRepo) SearchMyUserGrants(ctx context.Context, request *gran } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/user_session.go b/internal/auth/repository/eventsourcing/eventstore/user_session.go index e3a8b53f6b..28246193c3 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user_session.go +++ b/internal/auth/repository/eventsourcing/eventstore/user_session.go @@ -20,3 +20,8 @@ func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_mode } return model.UserSessionsToModel(userSessions), nil } + +func (repo *UserSessionRepo) ActiveUserSessionCount() int64 { + userSessions, _ := repo.View.ActiveUserSessions() + return int64(len(userSessions)) +} diff --git a/internal/auth/repository/eventsourcing/handler/application.go b/internal/auth/repository/eventsourcing/handler/application.go index eccd0d36ab..377e0b0499 100644 --- a/internal/auth/repository/eventsourcing/handler/application.go +++ b/internal/auth/repository/eventsourcing/handler/application.go @@ -22,12 +22,12 @@ const ( applicationTable = "auth.applications" ) -func (p *Application) ViewModel() string { +func (a *Application) ViewModel() string { return applicationTable } -func (p *Application) EventQuery() (*models.SearchQuery, error) { - sequence, err := p.view.GetLatestApplicationSequence() +func (a *Application) EventQuery() (*models.SearchQuery, error) { + sequence, err := a.view.GetLatestApplicationSequence() if err != nil { return nil, err } @@ -65,33 +65,37 @@ func (a *Application) Reduce(event *models.Event) (err error) { if err != nil { return err } - return a.view.DeleteApplication(app.ID, event.Sequence) + return a.view.DeleteApplication(app.ID, event.Sequence, event.CreationDate) case es_model.ProjectChanged: apps, err := a.view.ApplicationsByProjectID(event.AggregateID) if err != nil { return err } if len(apps) == 0 { - return a.view.ProcessedApplicationSequence(event.Sequence) + return a.view.ProcessedApplicationSequence(event.Sequence, event.CreationDate) } for _, app := range apps { if err := app.AppendEvent(event); err != nil { return err } } - return a.view.PutApplications(apps, event.Sequence) + return a.view.PutApplications(apps, event.Sequence, event.CreationDate) case es_model.ProjectRemoved: return a.view.DeleteApplicationsByProjectID(event.AggregateID) default: - return a.view.ProcessedApplicationSequence(event.Sequence) + return a.view.ProcessedApplicationSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return a.view.PutApplication(app) + return a.view.PutApplication(app, event.CreationDate) } -func (p *Application) OnError(event *models.Event, spoolerError error) error { +func (a *Application) OnError(event *models.Event, spoolerError error) error { logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler") - return spooler.HandleError(event, spoolerError, p.view.GetLatestApplicationFailedEvent, p.view.ProcessedApplicationFailedEvent, p.view.ProcessedApplicationSequence, p.errorCountUntilSkip) + return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip) +} + +func (a *Application) OnSuccess() error { + return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/idp_config.go b/internal/auth/repository/eventsourcing/handler/idp_config.go index 0460b32a9b..e6c21a6260 100644 --- a/internal/auth/repository/eventsourcing/handler/idp_config.go +++ b/internal/auth/repository/eventsourcing/handler/idp_config.go @@ -19,12 +19,12 @@ const ( idpConfigTable = "auth.idp_configs" ) -func (m *IDPConfig) ViewModel() string { +func (i *IDPConfig) ViewModel() string { return idpConfigTable } -func (m *IDPConfig) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestIDPConfigSequence() +func (i *IDPConfig) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestIDPConfigSequence() if err != nil { return nil, err } @@ -33,17 +33,17 @@ func (m *IDPConfig) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *IDPConfig) Reduce(event *models.Event) (err error) { +func (i *IDPConfig) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate: - err = m.processIdpConfig(iam_model.IDPProviderTypeOrg, event) + err = i.processIdpConfig(iam_model.IDPProviderTypeOrg, event) case iam_es_model.IAMAggregate: - err = m.processIdpConfig(iam_model.IDPProviderTypeSystem, event) + err = i.processIdpConfig(iam_model.IDPProviderTypeSystem, event) } return err } -func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, event *models.Event) (err error) { +func (i *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, event *models.Event) (err error) { idp := new(iam_view_model.IDPConfigView) switch event.Type { case model.IDPConfigAdded, @@ -56,7 +56,7 @@ func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve if err != nil { return err } - idp, err = m.view.IDPConfigByID(idp.IDPConfigID) + idp, err = i.view.IDPConfigByID(idp.IDPConfigID) if err != nil { return err } @@ -66,17 +66,21 @@ func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve if err != nil { return err } - return m.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence) + return i.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIDPConfigSequence(event.Sequence) + return i.view.ProcessedIDPConfigSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIDPConfig(idp, idp.Sequence) + return i.view.PutIDPConfig(idp, idp.Sequence, event.CreationDate) } -func (m *IDPConfig) OnError(event *models.Event, err error) error { +func (i *IDPConfig) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Ejf8s", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler") - return spooler.HandleError(event, err, m.view.GetLatestIDPConfigFailedEvent, m.view.ProcessedIDPConfigFailedEvent, m.view.ProcessedIDPConfigSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestIDPConfigFailedEvent, i.view.ProcessedIDPConfigFailedEvent, i.view.ProcessedIDPConfigSequence, i.errorCountUntilSkip) +} + +func (i *IDPConfig) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateIDPConfigSpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/idp_providers.go b/internal/auth/repository/eventsourcing/handler/idp_providers.go index b4c54a2834..b92a20a3b4 100644 --- a/internal/auth/repository/eventsourcing/handler/idp_providers.go +++ b/internal/auth/repository/eventsourcing/handler/idp_providers.go @@ -27,12 +27,12 @@ const ( idpProviderTable = "auth.idp_providers" ) -func (m *IDPProvider) ViewModel() string { +func (i *IDPProvider) ViewModel() string { return idpProviderTable } -func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestIDPProviderSequence() +func (i *IDPProvider) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestIDPProviderSequence() if err != nil { return nil, err } @@ -41,15 +41,15 @@ func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *IDPProvider) Reduce(event *models.Event) (err error) { +func (i *IDPProvider) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.IAMAggregate, org_es_model.OrgAggregate: - err = m.processIdpProvider(event) + err = i.processIdpProvider(event) } return err } -func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { +func (i *IDPProvider) processIdpProvider(event *models.Event) (err error) { provider := new(iam_view_model.IDPProviderView) switch event.Type { case model.LoginPolicyIDPProviderAdded, org_es_model.LoginPolicyIDPProviderAdded: @@ -57,71 +57,75 @@ func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { if err != nil { return err } - err = m.fillData(provider) + err = i.fillData(provider) case model.LoginPolicyIDPProviderRemoved, model.LoginPolicyIDPProviderCascadeRemoved, org_es_model.LoginPolicyIDPProviderRemoved, org_es_model.LoginPolicyIDPProviderCascadeRemoved: err = provider.SetData(event) if err != nil { return err } - return m.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence) + return i.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence, event.CreationDate) case model.IDPConfigChanged, org_es_model.IDPConfigChanged: esConfig := new(iam_view_model.IDPConfigView) providerType := iam_model.IDPProviderTypeSystem - if event.AggregateID != m.systemDefaults.IamID { + if event.AggregateID != i.systemDefaults.IamID { providerType = iam_model.IDPProviderTypeOrg } esConfig.AppendEvent(providerType, event) - providers, err := m.view.IDPProvidersByIDPConfigID(esConfig.IDPConfigID) + providers, err := i.view.IDPProvidersByIDPConfigID(esConfig.IDPConfigID) if err != nil { return err } config := new(iam_model.IDPConfig) - if event.AggregateID == m.systemDefaults.IamID { - config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) + if event.AggregateID == i.systemDefaults.IamID { + config, err = i.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, esConfig.IDPConfigID) } if err != nil { return err } for _, provider := range providers { - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) } - return m.view.PutIDPProviders(event.Sequence, providers...) + return i.view.PutIDPProviders(event.Sequence, event.CreationDate, providers...) case org_es_model.LoginPolicyRemoved: - return m.view.DeleteIDPProvidersByAggregateID(event.AggregateID, event.Sequence) + return i.view.DeleteIDPProvidersByAggregateID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIDPProviderSequence(event.Sequence) + return i.view.ProcessedIDPProviderSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIDPProvider(provider, provider.Sequence) + return i.view.PutIDPProvider(provider, provider.Sequence, event.CreationDate) } -func (m *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { +func (i *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { var config *iam_model.IDPConfig if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) { - config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, provider.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), i.systemDefaults.IamID, provider.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID) } if err != nil { return err } - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) return nil } -func (m *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *iam_model.IDPConfig) { +func (i *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *iam_model.IDPConfig) { provider.Name = config.Name provider.StylingType = int32(config.StylingType) provider.IDPConfigType = int32(config.Type) provider.IDPState = int32(config.State) } -func (m *IDPProvider) OnError(event *models.Event, err error) error { +func (i *IDPProvider) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Fjd89", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestIDPProviderFailedEvent, m.view.ProcessedIDPProviderFailedEvent, m.view.ProcessedIDPProviderSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestIDPProviderFailedEvent, i.view.ProcessedIDPProviderFailedEvent, i.view.ProcessedIDPProviderSequence, i.errorCountUntilSkip) +} + +func (i *IDPProvider) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateIDPProviderSpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/key.go b/internal/auth/repository/eventsourcing/handler/key.go index e56e20d153..48f224cc29 100644 --- a/internal/auth/repository/eventsourcing/handler/key.go +++ b/internal/auth/repository/eventsourcing/handler/key.go @@ -41,11 +41,11 @@ func (k *Key) Reduce(event *models.Event) error { return err } if privateKey.Expiry.Before(time.Now()) && publicKey.Expiry.Before(time.Now()) { - return k.view.ProcessedKeySequence(event.Sequence) + return k.view.ProcessedKeySequence(event.Sequence, event.CreationDate) } - return k.view.PutKeys(privateKey, publicKey, event.Sequence) + return k.view.PutKeys(privateKey, publicKey, event.Sequence, event.CreationDate) default: - return k.view.ProcessedKeySequence(event.Sequence) + return k.view.ProcessedKeySequence(event.Sequence, event.CreationDate) } } @@ -53,3 +53,9 @@ func (k *Key) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-GHa3a", "id", event.AggregateID).WithError(err).Warn("something went wrong in key handler") return spooler.HandleError(event, err, k.view.GetLatestKeyFailedEvent, k.view.ProcessedKeyFailedEvent, k.view.ProcessedKeySequence, k.errorCountUntilSkip) } + +func (k *Key) OnSuccess() error { + err := spooler.HandleSuccess(k.view.UpdateKeySpoolerRunTimestamp) + logging.LogWithFields("SPOOL-vM9sd", "table", keyTable).OnError(err).Warn("could not process on success func") + return err +} diff --git a/internal/auth/repository/eventsourcing/handler/login_policy.go b/internal/auth/repository/eventsourcing/handler/login_policy.go index ca01a1ea76..b92dd17a9e 100644 --- a/internal/auth/repository/eventsourcing/handler/login_policy.go +++ b/internal/auth/repository/eventsourcing/handler/login_policy.go @@ -19,12 +19,12 @@ const ( loginPolicyTable = "auth.login_policies" ) -func (m *LoginPolicy) ViewModel() string { +func (p *LoginPolicy) ViewModel() string { return loginPolicyTable } -func (m *LoginPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestLoginPolicySequence() +func (p *LoginPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestLoginPolicySequence() if err != nil { return nil, err } @@ -33,15 +33,15 @@ func (m *LoginPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *LoginPolicy) Reduce(event *models.Event) (err error) { +func (p *LoginPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processLoginPolicy(event) + err = p.processLoginPolicy(event) } return err } -func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { +func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { policy := new(iam_model.LoginPolicyView) switch event.Type { case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: @@ -51,23 +51,27 @@ func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { iam_es_model.LoginPolicySecondFactorRemoved, model.LoginPolicySecondFactorRemoved, iam_es_model.LoginPolicyMultiFactorAdded, model.LoginPolicyMultiFactorAdded, iam_es_model.LoginPolicyMultiFactorRemoved, model.LoginPolicyMultiFactorRemoved: - policy, err = m.view.LoginPolicyByAggregateID(event.AggregateID) + policy, err = p.view.LoginPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.LoginPolicyRemoved: - return m.view.DeleteLoginPolicy(event.AggregateID, event.Sequence) + return p.view.DeleteLoginPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedLoginPolicySequence(event.Sequence) + return p.view.ProcessedLoginPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutLoginPolicy(policy, policy.Sequence) + return p.view.PutLoginPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *LoginPolicy) OnError(event *models.Event, err error) error { +func (p *LoginPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-5id9s", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") - return spooler.HandleError(event, err, m.view.GetLatestLoginPolicyFailedEvent, m.view.ProcessedLoginPolicyFailedEvent, m.view.ProcessedLoginPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestLoginPolicyFailedEvent, p.view.ProcessedLoginPolicyFailedEvent, p.view.ProcessedLoginPolicySequence, p.errorCountUntilSkip) +} + +func (p *LoginPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/machine_keys.go b/internal/auth/repository/eventsourcing/handler/machine_keys.go index 84513ae0fa..f9af7e86fb 100644 --- a/internal/auth/repository/eventsourcing/handler/machine_keys.go +++ b/internal/auth/repository/eventsourcing/handler/machine_keys.go @@ -48,26 +48,30 @@ func (d *MachineKeys) processMachineKeys(event *models.Event) (err error) { case model.MachineKeyAdded: err = key.AppendEvent(event) if key.ExpirationDate.Before(time.Now()) { - return d.view.ProcessedMachineKeySequence(event.Sequence) + return d.view.ProcessedMachineKeySequence(event.Sequence, event.CreationDate) } case model.MachineKeyRemoved: err = key.SetData(event) if err != nil { return err } - return d.view.DeleteMachineKey(key.ID, event.Sequence) + return d.view.DeleteMachineKey(key.ID, event.Sequence, event.CreationDate) case model.UserRemoved: - return d.view.DeleteMachineKeysByUserID(event.AggregateID, event.Sequence) + return d.view.DeleteMachineKeysByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return d.view.ProcessedMachineKeySequence(event.Sequence) + return d.view.ProcessedMachineKeySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return d.view.PutMachineKey(key, key.Sequence) + return d.view.PutMachineKey(key, key.Sequence, event.CreationDate) } func (d *MachineKeys) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler") return spooler.HandleError(event, err, d.view.GetLatestMachineKeyFailedEvent, d.view.ProcessedMachineKeyFailedEvent, d.view.ProcessedMachineKeySequence, d.errorCountUntilSkip) } + +func (d *MachineKeys) OnSuccess() error { + return spooler.HandleSuccess(d.view.UpdateMachineKeySpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/org.go b/internal/auth/repository/eventsourcing/handler/org.go index 58bafb6ac0..909cfefc4a 100644 --- a/internal/auth/repository/eventsourcing/handler/org.go +++ b/internal/auth/repository/eventsourcing/handler/org.go @@ -58,16 +58,20 @@ func (o *Org) Reduce(event *es_models.Event) (err error) { } org.Domain = domain.Domain default: - return o.view.ProcessedOrgSequence(event.Sequence) + return o.view.ProcessedOrgSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return o.view.PutOrg(org) + return o.view.PutOrg(org, event.CreationDate) } func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler") return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) } + +func (o *Org) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/org_iam_policy.go b/internal/auth/repository/eventsourcing/handler/org_iam_policy.go index 55f72f74d7..ac28b5c7c4 100644 --- a/internal/auth/repository/eventsourcing/handler/org_iam_policy.go +++ b/internal/auth/repository/eventsourcing/handler/org_iam_policy.go @@ -19,12 +19,12 @@ const ( orgIAMPolicyTable = "auth.org_iam_policies" ) -func (m *OrgIAMPolicy) ViewModel() string { +func (p *OrgIAMPolicy) ViewModel() string { return orgIAMPolicyTable } -func (m *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestOrgIAMPolicySequence() +func (p *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestOrgIAMPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *OrgIAMPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *OrgIAMPolicy) Reduce(event *models.Event) (err error) { +func (p *OrgIAMPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processOrgIAMPolicy(event) + err = p.processOrgIAMPolicy(event) } return err } -func (m *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) { +func (p *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) { policy := new(iam_model.OrgIAMPolicyView) switch event.Type { case iam_es_model.OrgIAMPolicyAdded, model.OrgIAMPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.OrgIAMPolicyChanged, model.OrgIAMPolicyChanged: - policy, err = m.view.OrgIAMPolicyByAggregateID(event.AggregateID) + policy, err = p.view.OrgIAMPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.OrgIAMPolicyRemoved: - return m.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence) + return p.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedOrgIAMPolicySequence(event.Sequence) + return p.view.ProcessedOrgIAMPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutOrgIAMPolicy(policy, policy.Sequence) + return p.view.PutOrgIAMPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *OrgIAMPolicy) OnError(event *models.Event, err error) error { +func (p *OrgIAMPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-3Gj8s", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgIAM policy handler") - return spooler.HandleError(event, err, m.view.GetLatestOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicyFailedEvent, p.view.ProcessedOrgIAMPolicySequence, p.errorCountUntilSkip) +} + +func (p *OrgIAMPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateOrgIAMPolicySpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/password_complexity_policy.go b/internal/auth/repository/eventsourcing/handler/password_complexity_policy.go index c280615988..d3656a164a 100644 --- a/internal/auth/repository/eventsourcing/handler/password_complexity_policy.go +++ b/internal/auth/repository/eventsourcing/handler/password_complexity_policy.go @@ -19,12 +19,12 @@ const ( passwordComplexityPolicyTable = "auth.password_complexity_policies" ) -func (m *PasswordComplexityPolicy) ViewModel() string { +func (p *PasswordComplexityPolicy) ViewModel() string { return passwordComplexityPolicyTable } -func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordComplexityPolicySequence() +func (p *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordComplexityPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordComplexityPolicy(event) + err = p.processPasswordComplexityPolicy(event) } return err } -func (m *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordComplexityPolicyView) switch event.Type { case iam_es_model.PasswordComplexityPolicyAdded, model.PasswordComplexityPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordComplexityPolicyChanged, model.PasswordComplexityPolicyChanged: - policy, err = m.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordComplexityPolicyRemoved: - return m.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordComplexityPolicySequence(event.Sequence) + return p.view.ProcessedPasswordComplexityPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordComplexityPolicy(policy, policy.Sequence) + return p.view.PutPasswordComplexityPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { +func (p *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordComplexity policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordComplexityPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdatePasswordComplexityPolicySpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/project_role.go b/internal/auth/repository/eventsourcing/handler/project_role.go index e877c867a4..c469019b9f 100644 --- a/internal/auth/repository/eventsourcing/handler/project_role.go +++ b/internal/auth/repository/eventsourcing/handler/project_role.go @@ -52,19 +52,23 @@ func (p *ProjectRole) Reduce(event *models.Event) (err error) { if err != nil { return err } - return p.view.DeleteProjectRole(event.AggregateID, event.ResourceOwner, role.Key, event.Sequence) + return p.view.DeleteProjectRole(event.AggregateID, event.ResourceOwner, role.Key, event.Sequence, event.CreationDate) case es_model.ProjectRemoved: return p.view.DeleteProjectRolesByProjectID(event.AggregateID) default: - return p.view.ProcessedProjectRoleSequence(event.Sequence) + return p.view.ProcessedProjectRoleSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProjectRole(role) + return p.view.PutProjectRole(role, event.CreationDate) } func (p *ProjectRole) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-lso9w", "id", event.AggregateID).WithError(err).Warn("something went wrong in project role handler") return spooler.HandleError(event, err, p.view.GetLatestProjectRoleFailedEvent, p.view.ProcessedProjectRoleFailedEvent, p.view.ProcessedProjectRoleSequence, p.errorCountUntilSkip) } + +func (p *ProjectRole) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectRoleSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/token.go b/internal/auth/repository/eventsourcing/handler/token.go index 8fb4c65739..2c056cc064 100644 --- a/internal/auth/repository/eventsourcing/handler/token.go +++ b/internal/auth/repository/eventsourcing/handler/token.go @@ -49,7 +49,7 @@ func (t *Token) Reduce(event *models.Event) (err error) { if err != nil { return err } - return t.view.PutToken(token) + return t.view.PutToken(token, event.CreationDate) case user_es_model.UserProfileChanged, user_es_model.HumanProfileChanged: user := new(view_model.UserView) @@ -61,25 +61,25 @@ func (t *Token) Reduce(event *models.Event) (err error) { for _, token := range tokens { token.PreferredLanguage = user.PreferredLanguage } - return t.view.PutTokens(tokens, event.Sequence) + return t.view.PutTokens(tokens, event.Sequence, event.CreationDate) case user_es_model.SignedOut, user_es_model.HumanSignedOut: id, err := agentIDFromSession(event) if err != nil { return err } - return t.view.DeleteSessionTokens(id, event.AggregateID, event.Sequence) + return t.view.DeleteSessionTokens(id, event.AggregateID, event.Sequence, event.CreationDate) case user_es_model.UserLocked, user_es_model.UserDeactivated, user_es_model.UserRemoved: - return t.view.DeleteUserTokens(event.AggregateID, event.Sequence) + return t.view.DeleteUserTokens(event.AggregateID, event.Sequence, event.CreationDate) case project_es_model.ApplicationDeactivated, project_es_model.ApplicationRemoved: application, err := applicationFromSession(event) if err != nil { return err } - return t.view.DeleteApplicationTokens(event.Sequence, application.AppID) + return t.view.DeleteApplicationTokens(event.Sequence, event.CreationDate, application.AppID) case project_es_model.ProjectDeactivated, project_es_model.ProjectRemoved: project, err := t.ProjectEvents.ProjectByID(context.Background(), event.AggregateID) @@ -90,9 +90,9 @@ func (t *Token) Reduce(event *models.Event) (err error) { for _, app := range project.Applications { applicationsIDs = append(applicationsIDs, app.AppID) } - return t.view.DeleteApplicationTokens(event.Sequence, applicationsIDs...) + return t.view.DeleteApplicationTokens(event.Sequence, event.CreationDate, applicationsIDs...) default: - return t.view.ProcessedTokenSequence(event.Sequence) + return t.view.ProcessedTokenSequence(event.Sequence, event.CreationDate) } } @@ -118,3 +118,7 @@ func applicationFromSession(event *models.Event) (*project_es_model.Application, } return application, nil } + +func (t *Token) OnSuccess() error { + return spooler.HandleSuccess(t.view.UpdateTokenSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/user.go b/internal/auth/repository/eventsourcing/handler/user.go index c05c2f0f73..8b1ceb091c 100644 --- a/internal/auth/repository/eventsourcing/handler/user.go +++ b/internal/auth/repository/eventsourcing/handler/user.go @@ -67,7 +67,7 @@ func (u *User) ProcessUser(event *models.Event) (err error) { if err != nil { return err } - u.fillLoginNames(user) + err = u.fillLoginNames(user) case es_model.UserProfileChanged, es_model.UserEmailChanged, es_model.UserEmailVerified, @@ -94,6 +94,12 @@ func (u *User) ProcessUser(event *models.Event) (err error) { es_model.HumanMFAOTPAdded, es_model.HumanMFAOTPVerified, es_model.HumanMFAOTPRemoved, + es_model.HumanMFAU2FTokenAdded, + es_model.HumanMFAU2FTokenVerified, + es_model.HumanMFAU2FTokenRemoved, + es_model.HumanPasswordlessTokenAdded, + es_model.HumanPasswordlessTokenVerified, + es_model.HumanPasswordlessTokenRemoved, es_model.HumanMFAInitSkipped, es_model.MachineChanged, es_model.HumanPasswordChanged: @@ -114,14 +120,14 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - return u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutUser(user, user.Sequence) + return u.view.PutUser(user, user.Sequence, event.CreationDate) } func (u *User) fillLoginNames(user *view_model.UserView) (err error) { @@ -152,7 +158,7 @@ func (u *User) ProcessOrg(event *models.Event) (err error) { case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } } @@ -175,7 +181,7 @@ func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.SetLoginNames(policy, org.Domains) } - return u.view.PutUsers(users, event.Sequence) + return u.view.PutUsers(users, event.Sequence, event.CreationDate) } func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { @@ -200,10 +206,14 @@ func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain) } - return u.view.PutUsers(users, 0) + return u.view.PutUsers(users, 0, event.CreationDate) } func (u *User) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-is8aAWima", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler") return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip) } + +func (u *User) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/user_external_idps.go b/internal/auth/repository/eventsourcing/handler/user_external_idps.go index 5de4130bcf..826d79170c 100644 --- a/internal/auth/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/auth/repository/eventsourcing/handler/user_external_idps.go @@ -29,12 +29,12 @@ const ( externalIDPTable = "auth.user_external_idps" ) -func (m *ExternalIDP) ViewModel() string { +func (i *ExternalIDP) ViewModel() string { return externalIDPTable } -func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestExternalIDPSequence() +func (i *ExternalIDP) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestExternalIDPSequence() if err != nil { return nil, err } @@ -43,17 +43,17 @@ func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *ExternalIDP) Reduce(event *models.Event) (err error) { +func (i *ExternalIDP) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.UserAggregate: - err = m.processUser(event) + err = i.processUser(event) case iam_es_model.IAMAggregate, org_es_model.OrgAggregate: - err = m.processIdpConfig(event) + err = i.processIdpConfig(event) } return err } -func (m *ExternalIDP) processUser(event *models.Event) (err error) { +func (i *ExternalIDP) processUser(event *models.Event) (err error) { externalIDP := new(usr_view_model.ExternalIDPView) switch event.Type { case model.HumanExternalIDPAdded: @@ -61,25 +61,25 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { if err != nil { return err } - err = m.fillData(externalIDP) + err = i.fillData(externalIDP) case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved: err = externalIDP.SetData(event) if err != nil { return err } - return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence, event.CreationDate) case model.UserRemoved: - return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) + return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence) + return i.view.PutExternalIDP(externalIDP, externalIDP.Sequence, event.CreationDate) } -func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { +func (i *ExternalIDP) processIdpConfig(event *models.Event) (err error) { switch event.Type { case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: configView := new(iam_view_model.IDPConfigView) @@ -89,45 +89,49 @@ func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { } else { configView.AppendEvent(iam_model.IDPProviderTypeOrg, event) } - exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) + exterinalIDPs, err := i.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) if err != nil { return err } if event.AggregateType == iam_es_model.IAMAggregate { - config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } if err != nil { return err } for _, provider := range exterinalIDPs { - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) } - return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...) + return i.view.PutExternalIDPs(event.Sequence, event.CreationDate, exterinalIDPs...) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } return nil } -func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { - config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) +func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { + config, err := i.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) if caos_errs.IsNotFound(err) { - config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), i.systemDefaults.IamID, externalIDP.IDPConfigID) } if err != nil { return err } - m.fillConfigData(externalIDP, config) + i.fillConfigData(externalIDP, config) return nil } -func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { +func (i *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { externalIDP.IDPName = config.Name } -func (m *ExternalIDP) OnError(event *models.Event, err error) error { +func (i *ExternalIDP) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestExternalIDPFailedEvent, i.view.ProcessedExternalIDPFailedEvent, i.view.ProcessedExternalIDPSequence, i.errorCountUntilSkip) +} + +func (i *ExternalIDP) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateExternalIDPSpoolerRunTimestamp) } diff --git a/internal/auth/repository/eventsourcing/handler/user_grant.go b/internal/auth/repository/eventsourcing/handler/user_grant.go index d9ded99e7f..c61f831162 100644 --- a/internal/auth/repository/eventsourcing/handler/user_grant.go +++ b/internal/auth/repository/eventsourcing/handler/user_grant.go @@ -97,14 +97,14 @@ func (u *UserGrant) processUserGrant(event *models.Event) (err error) { } err = grant.AppendEvent(event) case grant_es_model.UserGrantRemoved, grant_es_model.UserGrantCascadeRemoved: - return u.view.DeleteUserGrant(event.AggregateID, event.Sequence) + return u.view.DeleteUserGrant(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutUserGrant(grant, grant.Sequence) + return u.view.PutUserGrant(grant, grant.Sequence, event.CreationDate) } func (u *UserGrant) processUser(event *models.Event) (err error) { @@ -119,7 +119,7 @@ func (u *UserGrant) processUser(event *models.Event) (err error) { return err } if len(grants) == 0 { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } user, err := u.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -128,9 +128,9 @@ func (u *UserGrant) processUser(event *models.Event) (err error) { for _, grant := range grants { u.fillUserData(grant, user) } - return u.view.PutUserGrants(grants, event.Sequence) + return u.view.PutUserGrants(grants, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -148,7 +148,7 @@ func (u *UserGrant) processProject(event *models.Event) (err error) { for _, grant := range grants { u.fillProjectData(grant, project) } - return u.view.PutUserGrants(grants, event.Sequence) + return u.view.PutUserGrants(grants, event.Sequence, event.CreationDate) case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectMemberRemoved: member := new(proj_es_model.ProjectMember) member.SetData(event) @@ -158,7 +158,7 @@ func (u *UserGrant) processProject(event *models.Event) (err error) { member.SetData(event) return u.processMember(event, "PROJECT_GRANT", member.GrantID, member.UserID, member.Roles) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -169,7 +169,7 @@ func (u *UserGrant) processOrg(event *models.Event) (err error) { member.SetData(event) return u.processMember(event, "ORG", "", member.UserID, member.Roles) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -207,16 +207,16 @@ func (u *UserGrant) processIAMMember(event *models.Event, rolePrefix string, suf } grant.Sequence = event.Sequence grant.ChangeDate = event.CreationDate - return u.view.PutUserGrant(grant, grant.Sequence) + return u.view.PutUserGrant(grant, grant.Sequence, event.CreationDate) case iam_es_model.IAMMemberRemoved: member.SetData(event) grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID) if err != nil { return err } - return u.view.DeleteUserGrant(grant.ID, event.Sequence) + return u.view.DeleteUserGrant(grant.ID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -252,7 +252,7 @@ func (u *UserGrant) processMember(event *models.Event, rolePrefix, roleSuffix st } grant.Sequence = event.Sequence grant.ChangeDate = event.CreationDate - return u.view.PutUserGrant(grant, event.Sequence) + return u.view.PutUserGrant(grant, event.Sequence, event.CreationDate) case org_es_model.OrgMemberRemoved, proj_es_model.ProjectMemberRemoved, proj_es_model.ProjectGrantMemberRemoved: @@ -262,18 +262,18 @@ func (u *UserGrant) processMember(event *models.Event, rolePrefix, roleSuffix st return err } if errors.IsNotFound(err) { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } if roleSuffix != "" { roleKeys = suffixRoles(roleSuffix, roleKeys) } if grant.RoleKeys == nil { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } grant.RoleKeys = mergeExistingRoles(rolePrefix, roleSuffix, grant.RoleKeys, nil) - return u.view.PutUserGrant(grant, event.Sequence) + return u.view.PutUserGrant(grant, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -367,3 +367,7 @@ func (u *UserGrant) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-UZmc7", "id", event.AggregateID).WithError(err).Warn("something went wrong in user grant handler") return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip) } + +func (u *UserGrant) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserGrantSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/user_membership.go b/internal/auth/repository/eventsourcing/handler/user_membership.go index 7a1145d224..7b231a78b5 100644 --- a/internal/auth/repository/eventsourcing/handler/user_membership.go +++ b/internal/auth/repository/eventsourcing/handler/user_membership.go @@ -74,14 +74,14 @@ func (m *UserMembership) processIam(event *models.Event) (err error) { } err = member.AppendEvent(event) case iam_es_model.IAMMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillIamDisplayName(member *usr_es_model.UserMembershipView) { @@ -105,16 +105,16 @@ func (m *UserMembership) processOrg(event *models.Event) (err error) { } err = member.AppendEvent(event) case org_es_model.OrgMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence, event.CreationDate) case org_es_model.OrgChanged: return m.updateOrgName(event) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillOrgName(member *usr_es_model.UserMembershipView) (err error) { @@ -145,7 +145,7 @@ func (m *UserMembership) updateOrgName(event *models.Event) error { membership.DisplayName = org.Name } } - return m.view.BulkPutUserMemberships(memberships, event.Sequence) + return m.view.BulkPutUserMemberships(memberships, event.Sequence, event.CreationDate) } func (m *UserMembership) processProject(event *models.Event) (err error) { @@ -168,7 +168,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) { } err = member.AppendEvent(event) case proj_es_model.ProjectMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence, event.CreationDate) case proj_es_model.ProjectGrantMemberChanged: member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant) if err != nil { @@ -176,20 +176,20 @@ func (m *UserMembership) processProject(event *models.Event) (err error) { } err = member.AppendEvent(event) case proj_es_model.ProjectGrantMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence, event.CreationDate) case proj_es_model.ProjectChanged: return m.updateProjectDisplayName(event) case proj_es_model.ProjectRemoved: - return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence) + return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence, event.CreationDate) case proj_es_model.ProjectGrantRemoved: - return m.view.DeleteUserMembershipsByAggregateIDAndObjectID(event.AggregateID, member.ObjectID, event.Sequence) + return m.view.DeleteUserMembershipsByAggregateIDAndObjectID(event.AggregateID, member.ObjectID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillProjectDisplayName(member *usr_es_model.UserMembershipView) (err error) { @@ -214,15 +214,15 @@ func (m *UserMembership) updateProjectDisplayName(event *models.Event) error { for _, membership := range memberships { membership.DisplayName = project.Name } - return m.view.BulkPutUserMemberships(memberships, event.Sequence) + return m.view.BulkPutUserMemberships(memberships, event.Sequence, event.CreationDate) } func (m *UserMembership) processUser(event *models.Event) (err error) { switch event.Type { case model.UserRemoved: - return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence) + return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } } @@ -230,3 +230,7 @@ func (m *UserMembership) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in user membership handler") return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip) } + +func (m *UserMembership) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateUserMembershipSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/user_session.go b/internal/auth/repository/eventsourcing/handler/user_session.go index 305d5cd04d..174a0d8394 100644 --- a/internal/auth/repository/eventsourcing/handler/user_session.go +++ b/internal/auth/repository/eventsourcing/handler/user_session.go @@ -48,6 +48,10 @@ func (u *UserSession) Reduce(event *models.Event) (err error) { es_model.HumanExternalLoginCheckSucceeded, es_model.HumanMFAOTPCheckSucceeded, es_model.HumanMFAOTPCheckFailed, + es_model.HumanMFAU2FTokenCheckSucceeded, + es_model.HumanMFAU2FTokenCheckFailed, + es_model.HumanPasswordlessTokenCheckSucceeded, + es_model.HumanPasswordlessTokenCheckFailed, es_model.HumanSignedOut: eventData, err := view_model.UserSessionFromEvent(event) if err != nil { @@ -78,13 +82,15 @@ func (u *UserSession) Reduce(event *models.Event) (err error) { es_model.DomainClaimed, es_model.UserUserNameChanged, es_model.HumanExternalIDPRemoved, - es_model.HumanExternalIDPCascadeRemoved: + es_model.HumanExternalIDPCascadeRemoved, + es_model.HumanPasswordlessTokenRemoved, + es_model.HumanMFAU2FTokenRemoved: sessions, err := u.view.UserSessionsByUserID(event.AggregateID) if err != nil { return err } if len(sessions) == 0 { - return u.view.ProcessedUserSessionSequence(event.Sequence) + return u.view.ProcessedUserSessionSequence(event.Sequence, event.CreationDate) } for _, session := range sessions { session.AppendEvent(event) @@ -92,11 +98,11 @@ func (u *UserSession) Reduce(event *models.Event) (err error) { return err } } - return u.view.PutUserSessions(sessions, event.Sequence) + return u.view.PutUserSessions(sessions, event.Sequence, event.CreationDate) case es_model.UserRemoved: - return u.view.DeleteUserSessions(event.AggregateID, event.Sequence) + return u.view.DeleteUserSessions(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserSessionSequence(event.Sequence) + return u.view.ProcessedUserSessionSequence(event.Sequence, event.CreationDate) } } @@ -105,12 +111,16 @@ func (u *UserSession) OnError(event *models.Event, err error) error { return spooler.HandleError(event, err, u.view.GetLatestUserSessionFailedEvent, u.view.ProcessedUserSessionFailedEvent, u.view.ProcessedUserSessionSequence, u.errorCountUntilSkip) } +func (u *UserSession) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserSessionSpoolerRunTimestamp) +} + func (u *UserSession) updateSession(session *view_model.UserSessionView, event *models.Event) error { session.AppendEvent(event) if err := u.fillUserInfo(session, event.AggregateID); err != nil { return err } - return u.view.PutUserSession(session) + return u.view.PutUserSession(session, event.CreationDate) } func (u *UserSession) fillUserInfo(session *view_model.UserSessionView, id string) error { diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 89f2fa1569..5903edf52c 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -2,7 +2,6 @@ package eventsourcing import ( "context" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler" @@ -146,7 +145,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au IdGenerator: idGenerator, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, - MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration, + MFAInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MFAInitSkip.Duration, SecondFactorCheckLifeTime: systemDefaults.VerificationLifetimes.SecondFactorCheck.Duration, MultiFactorCheckLifeTime: systemDefaults.VerificationLifetimes.MultiFactorCheck.Duration, IAMID: systemDefaults.IamID, diff --git a/internal/auth/repository/eventsourcing/view/application.go b/internal/auth/repository/eventsourcing/view/application.go index 06e59881eb..556d31d251 100644 --- a/internal/auth/repository/eventsourcing/view/application.go +++ b/internal/auth/repository/eventsourcing/view/application.go @@ -8,6 +8,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -26,28 +27,28 @@ func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) return view.SearchApplications(v.Db, applicationTable, request) } -func (v *View) PutApplication(app *model.ApplicationView) error { +func (v *View) PutApplication(app *model.ApplicationView, eventTimestamp time.Time) error { err := view.PutApplication(v.Db, applicationTable, app) if err != nil { return err } - return v.ProcessedApplicationSequence(app.Sequence) + return v.ProcessedApplicationSequence(app.Sequence, eventTimestamp) } -func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64) error { +func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64, eventTimestamp time.Time) error { err := view.PutApplications(v.Db, applicationTable, apps...) if err != nil { return err } - return v.ProcessedApplicationSequence(sequence) + return v.ProcessedApplicationSequence(sequence, eventTimestamp) } -func (v *View) DeleteApplication(appID string, eventSequence uint64) error { +func (v *View) DeleteApplication(appID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteApplication(v.Db, applicationTable, appID) if err != nil { return nil } - return v.ProcessedApplicationSequence(eventSequence) + return v.ProcessedApplicationSequence(eventSequence, eventTimestamp) } func (v *View) DeleteApplicationsByProjectID(projectID string) error { @@ -58,8 +59,12 @@ func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, erro return v.latestSequence(applicationTable) } -func (v *View) ProcessedApplicationSequence(eventSequence uint64) error { - return v.saveCurrentSequence(applicationTable, eventSequence) +func (v *View) ProcessedApplicationSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(applicationTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateApplicationSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(applicationTable) } func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/external_idps.go b/internal/auth/repository/eventsourcing/view/external_idps.go index 6050dd73d8..1381611037 100644 --- a/internal/auth/repository/eventsourcing/view/external_idps.go +++ b/internal/auth/repository/eventsourcing/view/external_idps.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -32,44 +33,48 @@ func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ( return view.SearchExternalIDPs(v.Db, externalIDPTable, request) } -func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error { +func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64, eventTimestamp time.Time) error { err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error { +func (v *View) PutExternalIDPs(sequence uint64, eventTimestamp time.Time, externalIDPs ...*model.ExternalIDPView) error { err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) if err != nil { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } -func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error { - return v.saveCurrentSequence(externalIDPTable, eventSequence) +func (v *View) ProcessedExternalIDPSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(externalIDPTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateExternalIDPSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(externalIDPTable) } func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/idp_configs.go b/internal/auth/repository/eventsourcing/view/idp_configs.go index 0a218f08d6..df591a7a00 100644 --- a/internal/auth/repository/eventsourcing/view/idp_configs.go +++ b/internal/auth/repository/eventsourcing/view/idp_configs.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -24,28 +25,32 @@ func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*i return view.SearchIDPs(v.Db, idpConfigTable, request) } -func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, sequence uint64) error { +func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDP(v.Db, idpConfigTable, idp) if err != nil { return err } - return v.ProcessedIDPConfigSequence(sequence) + return v.ProcessedIDPConfigSequence(sequence, eventTimestamp) } -func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64) error { +func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDP(v.Db, idpConfigTable, idpID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPConfigSequence(eventSequence) + return v.ProcessedIDPConfigSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIDPConfigSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpConfigTable) } -func (v *View) ProcessedIDPConfigSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpConfigTable, eventSequence) +func (v *View) ProcessedIDPConfigSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpConfigTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIDPConfigSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpConfigTable) } func (v *View) GetLatestIDPConfigFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/idp_providers.go b/internal/auth/repository/eventsourcing/view/idp_providers.go index d60ef835d0..cf81e63745 100644 --- a/internal/auth/repository/eventsourcing/view/idp_providers.go +++ b/internal/auth/repository/eventsourcing/view/idp_providers.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -28,44 +29,48 @@ func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) ( return view.SearchIDPProviders(v.Db, idpProviderTable, request) } -func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64) error { +func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDPProvider(v.Db, idpProviderTable, provider) if err != nil { return err } - return v.ProcessedIDPProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) PutIDPProviders(sequence uint64, providers ...*model.IDPProviderView) error { +func (v *View) PutIDPProviders(sequence uint64, eventTimestamp time.Time, providers ...*model.IDPProviderView) error { err := view.PutIDPProviders(v.Db, idpProviderTable, providers...) if err != nil { return err } - return v.ProcessedIDPProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPProviderSequence(eventSequence) + return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteIDPProvidersByAggregateID(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteIDPProvidersByAggregateID(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDPProvidersByAggregateID(v.Db, idpProviderTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPProviderSequence(eventSequence) + return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIDPProviderSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpProviderTable) } -func (v *View) ProcessedIDPProviderSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpProviderTable, eventSequence) +func (v *View) ProcessedIDPProviderSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpProviderTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIDPProviderSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpProviderTable) } func (v *View) GetLatestIDPProviderFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/key.go b/internal/auth/repository/eventsourcing/view/key.go index 9a21afc4d0..c0c0f7d357 100644 --- a/internal/auth/repository/eventsourcing/view/key.go +++ b/internal/auth/repository/eventsourcing/view/key.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/key/repository/view" "github.com/caos/zitadel/internal/key/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -31,36 +32,40 @@ func (v *View) GetActiveKeySet() ([]*key_model.PublicKey, error) { return key_model.PublicKeysFromKeyView(model.KeyViewsToModel(keys), v.keyAlgorithm) } -func (v *View) PutKeys(privateKey, publicKey *model.KeyView, eventSequence uint64) error { +func (v *View) PutKeys(privateKey, publicKey *model.KeyView, eventSequence uint64, eventTimestamp time.Time) error { err := view.PutKeys(v.Db, keyTable, privateKey, publicKey) if err != nil { return err } - return v.ProcessedKeySequence(eventSequence) + return v.ProcessedKeySequence(eventSequence, eventTimestamp) } -func (v *View) DeleteKey(keyID string, private bool, eventSequence uint64) error { +func (v *View) DeleteKey(keyID string, private bool, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteKey(v.Db, keyTable, keyID, private) if err != nil { return nil } - return v.ProcessedKeySequence(eventSequence) + return v.ProcessedKeySequence(eventSequence, eventTimestamp) } -func (v *View) DeleteKeyPair(keyID string, eventSequence uint64) error { +func (v *View) DeleteKeyPair(keyID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteKeyPair(v.Db, keyTable, keyID) if err != nil { return nil } - return v.ProcessedKeySequence(eventSequence) + return v.ProcessedKeySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestKeySequence() (*repository.CurrentSequence, error) { return v.latestSequence(keyTable) } -func (v *View) ProcessedKeySequence(eventSequence uint64) error { - return v.saveCurrentSequence(keyTable, eventSequence) +func (v *View) ProcessedKeySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(keyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateKeySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(keyTable) } func (v *View) GetLatestKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/login_policies.go b/internal/auth/repository/eventsourcing/view/login_policies.go index 3a2133ddee..2da8862129 100644 --- a/internal/auth/repository/eventsourcing/view/login_policies.go +++ b/internal/auth/repository/eventsourcing/view/login_policies.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyV return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64) error { +func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) if err != nil { return err } - return v.ProcessedLoginPolicySequence(sequence) + return v.ProcessedLoginPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedLoginPolicySequence(eventSequence) + return v.ProcessedLoginPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(loginPolicyTable) } -func (v *View) ProcessedLoginPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(loginPolicyTable, eventSequence) +func (v *View) ProcessedLoginPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(loginPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(loginPolicyTable) } func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/machine_keys.go b/internal/auth/repository/eventsourcing/view/machine_keys.go index 03e7c494aa..ac1a442566 100644 --- a/internal/auth/repository/eventsourcing/view/machine_keys.go +++ b/internal/auth/repository/eventsourcing/view/machine_keys.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -27,39 +28,43 @@ func (v *View) SearchMachineKeys(request *usr_model.MachineKeySearchRequest) ([] return view.SearchMachineKeys(v.Db, machineKeyTable, request) } -func (v *View) PutMachineKey(key *model.MachineKeyView, sequence uint64) error { +func (v *View) PutMachineKey(key *model.MachineKeyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutMachineKey(v.Db, machineKeyTable, key) if err != nil { return err } if sequence != 0 { - return v.ProcessedMachineKeySequence(sequence) + return v.ProcessedMachineKeySequence(sequence, eventTimestamp) } return nil } -func (v *View) DeleteMachineKey(keyID string, eventSequence uint64) error { +func (v *View) DeleteMachineKey(keyID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteMachineKey(v.Db, machineKeyTable, keyID) if err != nil { return nil } - return v.ProcessedMachineKeySequence(eventSequence) + return v.ProcessedMachineKeySequence(eventSequence, eventTimestamp) } -func (v *View) DeleteMachineKeysByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteMachineKeysByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteMachineKey(v.Db, machineKeyTable, userID) if err != nil { return nil } - return v.ProcessedMachineKeySequence(eventSequence) + return v.ProcessedMachineKeySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestMachineKeySequence() (*repository.CurrentSequence, error) { return v.latestSequence(machineKeyTable) } -func (v *View) ProcessedMachineKeySequence(eventSequence uint64) error { - return v.saveCurrentSequence(machineKeyTable, eventSequence) +func (v *View) ProcessedMachineKeySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(machineKeyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateMachineKeySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(machineKeyTable) } func (v *View) GetLatestMachineKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/org.go b/internal/auth/repository/eventsourcing/view/org.go index 6383f62398..9f489f5c30 100644 --- a/internal/auth/repository/eventsourcing/view/org.go +++ b/internal/auth/repository/eventsourcing/view/org.go @@ -5,6 +5,7 @@ import ( org_view "github.com/caos/zitadel/internal/org/repository/view" org_model "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -23,12 +24,12 @@ func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, ui return org_view.SearchOrgs(v.Db, orgTable, req) } -func (v *View) PutOrg(org *org_model.OrgView) error { +func (v *View) PutOrg(org *org_model.OrgView, eventTimestamp time.Time) error { err := org_view.PutOrg(v.Db, orgTable, org) if err != nil { return err } - return v.ProcessedOrgSequence(org.Sequence) + return v.ProcessedOrgSequence(org.Sequence, eventTimestamp) } func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { @@ -39,10 +40,14 @@ func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) erro return v.saveFailedEvent(failedEvent) } +func (v *View) UpdateOrgSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgTable) +} + func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgTable) } -func (v *View) ProcessedOrgSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgTable, eventSequence) +func (v *View) ProcessedOrgSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgTable, eventSequence, eventTimestamp) } diff --git a/internal/auth/repository/eventsourcing/view/org_iam_policy.go b/internal/auth/repository/eventsourcing/view/org_iam_policy.go index 2d43efc80e..a9ee5a295f 100644 --- a/internal/auth/repository/eventsourcing/view/org_iam_policy.go +++ b/internal/auth/repository/eventsourcing/view/org_iam_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) OrgIAMPolicyByAggregateID(aggregateID string) (*model.OrgIAMPolic return view.GetOrgIAMPolicyByAggregateID(v.Db, orgIAMPolicyTable, aggregateID) } -func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64) error { +func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgIAMPolicy(v.Db, orgIAMPolicyTable, policy) if err != nil { return err } - return v.ProcessedOrgIAMPolicySequence(sequence) + return v.ProcessedOrgIAMPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgIAMPolicy(v.Db, orgIAMPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedOrgIAMPolicySequence(eventSequence) + return v.ProcessedOrgIAMPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestOrgIAMPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(orgIAMPolicyTable) } -func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence) +func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgIAMPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgIAMPolicyTable) } func (v *View) GetLatestOrgIAMPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/password_complexity_policy.go b/internal/auth/repository/eventsourcing/view/password_complexity_policy.go index 93e28e1eb6..8259c89da0 100644 --- a/internal/auth/repository/eventsourcing/view/password_complexity_policy.go +++ b/internal/auth/repository/eventsourcing/view/password_complexity_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordComplexityPolicyByAggregateID(aggregateID string) (*model return view.GetPasswordComplexityPolicyByAggregateID(v.Db, passwordComplexityPolicyTable, aggregateID) } -func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64) error { +func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordComplexityPolicySequence(sequence) + return v.ProcessedPasswordComplexityPolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordComplexityPolicySequence(eventSequence) + return v.ProcessedPasswordComplexityPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordComplexityPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordComplexityPolicyTable) } -func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence) +func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordComplexityPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordComplexityPolicyTable) } func (v *View) GetLatestPasswordComplexityPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/project_role.go b/internal/auth/repository/eventsourcing/view/project_role.go index fdfe4d9dc1..66c287ffa1 100644 --- a/internal/auth/repository/eventsourcing/view/project_role.go +++ b/internal/auth/repository/eventsourcing/view/project_role.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -31,20 +32,20 @@ func (v *View) SearchProjectRoles(request *proj_model.ProjectRoleSearchRequest) return view.SearchProjectRoles(v.Db, projectRoleTable, request) } -func (v *View) PutProjectRole(project *model.ProjectRoleView) error { +func (v *View) PutProjectRole(project *model.ProjectRoleView, eventTimestamp time.Time) error { err := view.PutProjectRole(v.Db, projectRoleTable, project) if err != nil { return err } - return v.ProcessedProjectRoleSequence(project.Sequence) + return v.ProcessedProjectRoleSequence(project.Sequence, eventTimestamp) } -func (v *View) DeleteProjectRole(projectID, orgID, key string, eventSequence uint64) error { +func (v *View) DeleteProjectRole(projectID, orgID, key string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProjectRole(v.Db, projectRoleTable, projectID, orgID, key) if err != nil { return nil } - return v.ProcessedProjectRoleSequence(eventSequence) + return v.ProcessedProjectRoleSequence(eventSequence, eventTimestamp) } func (v *View) DeleteProjectRolesByProjectID(projectID string) error { @@ -55,8 +56,12 @@ func (v *View) GetLatestProjectRoleSequence() (*repository.CurrentSequence, erro return v.latestSequence(projectRoleTable) } -func (v *View) ProcessedProjectRoleSequence(eventSequence uint64) error { - return v.saveCurrentSequence(projectRoleTable, eventSequence) +func (v *View) ProcessedProjectRoleSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(projectRoleTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectRoleSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(projectRoleTable) } func (v *View) GetLatestProjectRoleFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/sequence.go b/internal/auth/repository/eventsourcing/view/sequence.go index d65b6e4da6..4ac2ab80cc 100644 --- a/internal/auth/repository/eventsourcing/view/sequence.go +++ b/internal/auth/repository/eventsourcing/view/sequence.go @@ -2,16 +2,29 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( sequencesTable = "auth.current_sequences" ) -func (v *View) saveCurrentSequence(viewName string, sequence uint64) error { - return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence) +func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimestamp time.Time) error { + return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimestamp) } func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) { return repository.LatestSequence(v.Db, sequencesTable, viewName) } + +func (v *View) updateSpoolerRunSequence(viewName string) error { + currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName) + if err != nil { + return err + } + if currentSequence.ViewName == "" { + currentSequence.ViewName = viewName + } + currentSequence.LastSuccessfulSpoolerRun = time.Now() + return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence) +} diff --git a/internal/auth/repository/eventsourcing/view/token.go b/internal/auth/repository/eventsourcing/view/token.go index 94ecabd9b3..4cb7c41844 100644 --- a/internal/auth/repository/eventsourcing/view/token.go +++ b/internal/auth/repository/eventsourcing/view/token.go @@ -4,6 +4,7 @@ import ( usr_view "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -18,60 +19,64 @@ func (v *View) TokensByUserID(userID string) ([]*model.TokenView, error) { return usr_view.TokensByUserID(v.Db, tokenTable, userID) } -func (v *View) PutToken(token *model.TokenView) error { +func (v *View) PutToken(token *model.TokenView, eventTimestamp time.Time) error { err := usr_view.PutToken(v.Db, tokenTable, token) if err != nil { return err } - return v.ProcessedTokenSequence(token.Sequence) + return v.ProcessedTokenSequence(token.Sequence, eventTimestamp) } -func (v *View) PutTokens(token []*model.TokenView, sequence uint64) error { +func (v *View) PutTokens(token []*model.TokenView, sequence uint64, eventTimestamp time.Time) error { err := usr_view.PutTokens(v.Db, tokenTable, token...) if err != nil { return err } - return v.ProcessedTokenSequence(sequence) + return v.ProcessedTokenSequence(sequence, eventTimestamp) } -func (v *View) DeleteToken(tokenID string, eventSequence uint64) error { +func (v *View) DeleteToken(tokenID string, eventSequence uint64, eventTimestamp time.Time) error { err := usr_view.DeleteToken(v.Db, tokenTable, tokenID) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error { +func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := usr_view.DeleteSessionTokens(v.Db, tokenTable, agentID, userID) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserTokens(userID string, eventSequence uint64) error { +func (v *View) DeleteUserTokens(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := usr_view.DeleteUserTokens(v.Db, tokenTable, userID) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteApplicationTokens(eventSequence uint64, ids ...string) error { +func (v *View) DeleteApplicationTokens(eventSequence uint64, eventTimestamp time.Time, ids ...string) error { err := usr_view.DeleteApplicationTokens(v.Db, tokenTable, ids) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestTokenSequence() (*repository.CurrentSequence, error) { return v.latestSequence(tokenTable) } -func (v *View) ProcessedTokenSequence(eventSequence uint64) error { - return v.saveCurrentSequence(tokenTable, eventSequence) +func (v *View) ProcessedTokenSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(tokenTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateTokenSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(tokenTable) } func (v *View) GetLatestTokenFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/user.go b/internal/auth/repository/eventsourcing/view/user.go index 5472eb628d..e8b0591d22 100644 --- a/internal/auth/repository/eventsourcing/view/user.go +++ b/internal/auth/repository/eventsourcing/view/user.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -47,40 +48,44 @@ func (v *View) IsUserUnique(userName, email string) (bool, error) { return view.IsUserUnique(v.Db, userTable, userName, email) } -func (v *View) UserMfas(userID string) ([]*usr_model.MultiFactor, error) { - return view.UserMfas(v.Db, userTable, userID) +func (v *View) UserMFAs(userID string) ([]*usr_model.MultiFactor, error) { + return view.UserMFAs(v.Db, userTable, userID) } -func (v *View) PutUser(user *model.UserView, sequence uint64) error { +func (v *View) PutUser(user *model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUser(v.Db, userTable, user) if err != nil { return err } - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } -func (v *View) PutUsers(users []*model.UserView, sequence uint64) error { +func (v *View) PutUsers(users []*model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUsers(v.Db, userTable, users...) if err != nil { return err } - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } -func (v *View) DeleteUser(userID string, eventSequence uint64) error { +func (v *View) DeleteUser(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUser(v.Db, userTable, userID) if err != nil { return nil } - return v.ProcessedUserSequence(eventSequence) + return v.ProcessedUserSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userTable) } -func (v *View) ProcessedUserSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userTable, eventSequence) +func (v *View) ProcessedUserSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userTable) } func (v *View) GetLatestUserFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/user_grant.go b/internal/auth/repository/eventsourcing/view/user_grant.go index cc53b97504..230e9f5c8a 100644 --- a/internal/auth/repository/eventsourcing/view/user_grant.go +++ b/internal/auth/repository/eventsourcing/view/user_grant.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/usergrant/repository/view" "github.com/caos/zitadel/internal/usergrant/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -35,36 +36,40 @@ func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([] return view.SearchUserGrants(v.Db, userGrantTable, request) } -func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error { +func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserGrant(v.Db, userGrantTable, grant) if err != nil { return err } - return v.ProcessedUserGrantSequence(sequence) + return v.ProcessedUserGrantSequence(sequence, eventTimestamp) } -func (v *View) PutUserGrants(grants []*model.UserGrantView, sequence uint64) error { +func (v *View) PutUserGrants(grants []*model.UserGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserGrants(v.Db, userGrantTable, grants...) if err != nil { return err } - return v.ProcessedUserGrantSequence(sequence) + return v.ProcessedUserGrantSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error { +func (v *View) DeleteUserGrant(grantID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserGrant(v.Db, userGrantTable, grantID) if err != nil { return nil } - return v.ProcessedUserGrantSequence(eventSequence) + return v.ProcessedUserGrantSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserGrantSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userGrantTable) } -func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userGrantTable, eventSequence) +func (v *View) ProcessedUserGrantSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userGrantTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserGrantSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userGrantTable) } func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/user_membership.go b/internal/auth/repository/eventsourcing/view/user_membership.go index cbd241e350..84607584ab 100644 --- a/internal/auth/repository/eventsourcing/view/user_membership.go +++ b/internal/auth/repository/eventsourcing/view/user_membership.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -27,60 +28,64 @@ func (v *View) SearchUserMemberships(request *usr_model.UserMembershipSearchRequ return view.SearchUserMemberships(v.Db, userMembershipTable, request) } -func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64) error { +func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserMembership(v.Db, userMembershipTable, membership) if err != nil { return err } - return v.ProcessedUserMembershipSequence(sequence) + return v.ProcessedUserMembershipSequence(sequence, eventTimestamp) } -func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error { +func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserMemberships(v.Db, userMembershipTable, memberships...) if err != nil { return err } - return v.ProcessedUserMembershipSequence(sequence) + return v.ProcessedUserMembershipSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64) error { +func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembership(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByUserID(v.Db, userMembershipTable, userID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByAggregateID(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByAggregateID(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByAggregateID(v.Db, userMembershipTable, aggregateID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByAggregateIDAndObjectID(aggregateID, objectID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByAggregateIDAndObjectID(aggregateID, objectID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByAggregateIDAndObjectID(v.Db, userMembershipTable, aggregateID, objectID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userMembershipTable) } -func (v *View) ProcessedUserMembershipSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userMembershipTable, eventSequence) +func (v *View) ProcessedUserMembershipSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userMembershipTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserMembershipSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userMembershipTable) } func (v *View) GetLatestUserMembershipFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/eventsourcing/view/user_session.go b/internal/auth/repository/eventsourcing/view/user_session.go index fe9d6b0ec2..805cc04aef 100644 --- a/internal/auth/repository/eventsourcing/view/user_session.go +++ b/internal/auth/repository/eventsourcing/view/user_session.go @@ -4,6 +4,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -22,36 +23,44 @@ func (v *View) UserSessionsByAgentID(agentID string) ([]*model.UserSessionView, return view.UserSessionsByAgentID(v.Db, userSessionTable, agentID) } -func (v *View) PutUserSession(userSession *model.UserSessionView) error { +func (v *View) ActiveUserSessions() ([]*model.UserSessionView, error) { + return view.ActiveUserSessions(v.Db, userSessionTable) +} + +func (v *View) PutUserSession(userSession *model.UserSessionView, eventTimestamp time.Time) error { err := view.PutUserSession(v.Db, userSessionTable, userSession) if err != nil { return err } - return v.ProcessedUserSessionSequence(userSession.Sequence) + return v.ProcessedUserSessionSequence(userSession.Sequence, eventTimestamp) } -func (v *View) PutUserSessions(userSession []*model.UserSessionView, sequence uint64) error { +func (v *View) PutUserSessions(userSession []*model.UserSessionView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserSessions(v.Db, userSessionTable, userSession...) if err != nil { return err } - return v.ProcessedUserSessionSequence(sequence) + return v.ProcessedUserSessionSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserSessions(userID string, eventSequence uint64) error { +func (v *View) DeleteUserSessions(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserSessions(v.Db, userSessionTable, userID) if err != nil { return nil } - return v.ProcessedUserSessionSequence(eventSequence) + return v.ProcessedUserSessionSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserSessionSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userSessionTable) } -func (v *View) ProcessedUserSessionSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userSessionTable, eventSequence) +func (v *View) ProcessedUserSessionSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userSessionTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserSessionSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userSessionTable) } func (v *View) GetLatestUserSessionFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index 164ca23c35..81f6084e4c 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -13,7 +13,7 @@ type UserRepository interface { RegisterExternalUser(ctx context.Context, user *model.User, externalIDP *model.ExternalIDP, member *org_model.OrgMember, resourceOwner string) (*model.User, error) myUserRepo - SkipMfaInit(ctx context.Context, userID string) error + SkipMFAInit(ctx context.Context, userID string) error RequestPasswordReset(ctx context.Context, username string) error SetPassword(ctx context.Context, userID, code, password string) error @@ -25,8 +25,16 @@ type UserRepository interface { VerifyInitCode(ctx context.Context, userID, code, password string) error ResendInitVerificationMail(ctx context.Context, userID string) error - AddMfaOTP(ctx context.Context, userID string) (*model.OTP, error) - VerifyMfaOTPSetup(ctx context.Context, userID, code string) error + AddMFAOTP(ctx context.Context, userID string) (*model.OTP, error) + VerifyMFAOTPSetup(ctx context.Context, userID, code string) error + + AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error) + VerifyMFAU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error + RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error + + AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error) + VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error + RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error ChangeUsername(ctx context.Context, userID, username string) error @@ -63,10 +71,18 @@ type myUserRepo interface { AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error) RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error - MyUserMfas(ctx context.Context) ([]*model.MultiFactor, error) - AddMyMfaOTP(ctx context.Context) (*model.OTP, error) - VerifyMyMfaOTPSetup(ctx context.Context, code string) error - RemoveMyMfaOTP(ctx context.Context) error + MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) + AddMyMFAOTP(ctx context.Context) (*model.OTP, error) + VerifyMyMFAOTPSetup(ctx context.Context, code string) error + RemoveMyMFAOTP(ctx context.Context) error + + AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) + VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error + RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error + + AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) + VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error + RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error ChangeMyUsername(ctx context.Context, username string) error diff --git a/internal/auth/repository/user_session.go b/internal/auth/repository/user_session.go index 98e8751d07..f6eca64de2 100644 --- a/internal/auth/repository/user_session.go +++ b/internal/auth/repository/user_session.go @@ -7,4 +7,5 @@ import ( type UserSessionRepository interface { GetMyUserSessions(ctx context.Context) ([]*model.UserSessionView, error) + ActiveUserSessionCount() int64 } diff --git a/internal/auth_request/model/auth_request.go b/internal/auth_request/model/auth_request.go index adf36e27bc..bd51897afd 100644 --- a/internal/auth_request/model/auth_request.go +++ b/internal/auth_request/model/auth_request.go @@ -34,7 +34,7 @@ type AuthRequest struct { LinkingUsers []*ExternalUser PossibleSteps []NextStep PasswordVerified bool - MfasVerified []MFAType + MFAsVerified []MFAType Audience []string AuthTime time.Time Code string @@ -109,7 +109,7 @@ func (a *AuthRequest) IsValid() bool { a.Request != nil && a.Request.IsValid() } -func (a *AuthRequest) MfaLevel() MFALevel { +func (a *AuthRequest) MFALevel() MFALevel { return -1 //PLANNED: check a.PossibleLOAs (and Prompt Login?) } diff --git a/internal/auth_request/model/auth_request_test.go b/internal/auth_request/model/auth_request_test.go index 03319938e4..2e16f9b9a6 100644 --- a/internal/auth_request/model/auth_request_test.go +++ b/internal/auth_request/model/auth_request_test.go @@ -147,7 +147,7 @@ func TestAuthRequest_IsValid(t *testing.T) { } } -func TestAuthRequest_MfaLevel(t *testing.T) { +func TestAuthRequest_MFALevel(t *testing.T) { type fields struct { Prompt Prompt PossibleLOAs []LevelOfAssurance @@ -169,7 +169,7 @@ func TestAuthRequest_MfaLevel(t *testing.T) { Prompt: tt.fields.Prompt, PossibleLOAs: tt.fields.PossibleLOAs, } - if got := a.MfaLevel(); got != tt.want { + if got := a.MFALevel(); got != tt.want { t.Errorf("MFALevel() = %v, want %v", got, tt.want) } }) diff --git a/internal/auth_request/model/next_step.go b/internal/auth_request/model/next_step.go index 028e367127..3312244e11 100644 --- a/internal/auth_request/model/next_step.go +++ b/internal/auth_request/model/next_step.go @@ -15,14 +15,15 @@ const ( NextStepChangePassword NextStepInitPassword NextStepVerifyEmail - NextStepMfaPrompt - NextStepMfaVerify + NextStepMFAPrompt + NextStepMFAVerify NextStepRedirectToCallback NextStepChangeUsername NextStepLinkUsers NextStepExternalNotFoundOption NextStepExternalLogin NextStepGrantRequired + NextStepPasswordless ) type UserSessionState int32 @@ -81,6 +82,12 @@ func (s *ExternalLoginStep) Type() NextStepType { return NextStepExternalLogin } +type PasswordlessStep struct{} + +func (s *PasswordlessStep) Type() NextStepType { + return NextStepPasswordless +} + type ChangePasswordStep struct{} func (s *ChangePasswordStep) Type() NextStepType { @@ -105,21 +112,21 @@ func (s *VerifyEMailStep) Type() NextStepType { return NextStepVerifyEmail } -type MfaPromptStep struct { +type MFAPromptStep struct { Required bool - MfaProviders []MFAType + MFAProviders []MFAType } -func (s *MfaPromptStep) Type() NextStepType { - return NextStepMfaPrompt +func (s *MFAPromptStep) Type() NextStepType { + return NextStepMFAPrompt } -type MfaVerificationStep struct { - MfaProviders []MFAType +type MFAVerificationStep struct { + MFAProviders []MFAType } -func (s *MfaVerificationStep) Type() NextStepType { - return NextStepMfaVerify +func (s *MFAVerificationStep) Type() NextStepType { + return NextStepMFAVerify } type LinkUsersStep struct{} @@ -145,6 +152,7 @@ type MFAType int const ( MFATypeOTP MFAType = iota MFATypeU2F + MFATypeU2FUserVerification ) type MFALevel int diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 04b6966a97..a6a0160edf 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -15,7 +15,7 @@ import ( caos_errs "github.com/caos/zitadel/internal/errors" iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type TokenVerifierRepo struct { diff --git a/internal/authz/repository/eventsourcing/handler/application.go b/internal/authz/repository/eventsourcing/handler/application.go index 401f761ad0..4b5773607e 100644 --- a/internal/authz/repository/eventsourcing/handler/application.go +++ b/internal/authz/repository/eventsourcing/handler/application.go @@ -54,17 +54,21 @@ func (a *Application) Reduce(event *models.Event) (err error) { if err != nil { return err } - return a.view.DeleteApplication(app.ID, event.Sequence) + return a.view.DeleteApplication(app.ID, event.Sequence, event.CreationDate) default: - return a.view.ProcessedApplicationSequence(event.Sequence) + return a.view.ProcessedApplicationSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return a.view.PutApplication(app) + return a.view.PutApplication(app, event.CreationDate) } func (a *Application) OnError(event *models.Event, spoolerError error) error { logging.LogWithFields("SPOOL-sjZw", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler") return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip) } + +func (a *Application) OnSuccess() error { + return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp) +} diff --git a/internal/authz/repository/eventsourcing/handler/org.go b/internal/authz/repository/eventsourcing/handler/org.go index 79bc52b7b6..e487831f02 100644 --- a/internal/authz/repository/eventsourcing/handler/org.go +++ b/internal/authz/repository/eventsourcing/handler/org.go @@ -53,13 +53,17 @@ func (o *Org) Reduce(event *es_models.Event) error { return err } default: - return o.view.ProcessedOrgSequence(event.Sequence) + return o.view.ProcessedOrgSequence(event.Sequence, event.CreationDate) } - return o.view.PutOrg(org) + return o.view.PutOrg(org, event.CreationDate) } func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler") return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) } + +func (o *Org) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) +} diff --git a/internal/authz/repository/eventsourcing/handler/user_grant.go b/internal/authz/repository/eventsourcing/handler/user_grant.go index 897186b8e8..58cd01d31c 100644 --- a/internal/authz/repository/eventsourcing/handler/user_grant.go +++ b/internal/authz/repository/eventsourcing/handler/user_grant.go @@ -75,7 +75,7 @@ func (u *UserGrant) processProject(event *models.Event) (err error) { member.SetData(event) return u.processMember(event, "PROJECT_GRANT", member.GrantID, member.UserID, member.Roles) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -86,7 +86,7 @@ func (u *UserGrant) processOrg(event *models.Event) (err error) { member.SetData(event) return u.processMember(event, "ORG", "", member.UserID, member.Roles) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -124,16 +124,16 @@ func (u *UserGrant) processIAMMember(event *models.Event, rolePrefix string, suf } grant.Sequence = event.Sequence grant.ChangeDate = event.CreationDate - return u.view.PutUserGrant(grant, grant.Sequence) + return u.view.PutUserGrant(grant, grant.Sequence, event.CreationDate) case iam_es_model.IAMMemberRemoved: member.SetData(event) grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID) if err != nil { return err } - return u.view.DeleteUserGrant(grant.ID, event.Sequence) + return u.view.DeleteUserGrant(grant.ID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -169,7 +169,7 @@ func (u *UserGrant) processMember(event *models.Event, rolePrefix, roleSuffix st } grant.Sequence = event.Sequence grant.ChangeDate = event.CreationDate - return u.view.PutUserGrant(grant, event.Sequence) + return u.view.PutUserGrant(grant, event.Sequence, event.CreationDate) case org_es_model.OrgMemberRemoved, proj_es_model.ProjectMemberRemoved, proj_es_model.ProjectGrantMemberRemoved: @@ -179,18 +179,18 @@ func (u *UserGrant) processMember(event *models.Event, rolePrefix, roleSuffix st return err } if errors.IsNotFound(err) { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } if roleSuffix != "" { roleKeys = suffixRoles(roleSuffix, roleKeys) } if grant.RoleKeys == nil { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } grant.RoleKeys = mergeExistingRoles(rolePrefix, roleSuffix, grant.RoleKeys, nil) - return u.view.PutUserGrant(grant, event.Sequence) + return u.view.PutUserGrant(grant, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } } @@ -235,3 +235,7 @@ func (u *UserGrant) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-VcVoJ", "id", event.AggregateID).WithError(err).Warn("something went wrong in user grant handler") return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip) } + +func (u *UserGrant) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserGrantSpoolerRunTimestamp) +} diff --git a/internal/authz/repository/eventsourcing/view/application.go b/internal/authz/repository/eventsourcing/view/application.go index bb82b4d62f..a8e772b58d 100644 --- a/internal/authz/repository/eventsourcing/view/application.go +++ b/internal/authz/repository/eventsourcing/view/application.go @@ -2,11 +2,12 @@ package view import ( "context" + "time" proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/view/repository" ) @@ -33,28 +34,32 @@ func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) return view.SearchApplications(v.Db, applicationTable, request) } -func (v *View) PutApplication(project *model.ApplicationView) error { +func (v *View) PutApplication(project *model.ApplicationView, eventTimestamp time.Time) error { err := view.PutApplication(v.Db, applicationTable, project) if err != nil { return err } - return v.ProcessedApplicationSequence(project.Sequence) + return v.ProcessedApplicationSequence(project.Sequence, eventTimestamp) } -func (v *View) DeleteApplication(appID string, eventSequence uint64) error { +func (v *View) DeleteApplication(appID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteApplication(v.Db, applicationTable, appID) if err != nil { return nil } - return v.ProcessedApplicationSequence(eventSequence) + return v.ProcessedApplicationSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) { return v.latestSequence(applicationTable) } -func (v *View) ProcessedApplicationSequence(eventSequence uint64) error { - return v.saveCurrentSequence(applicationTable, eventSequence) +func (v *View) ProcessedApplicationSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(applicationTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateApplicationSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(applicationTable) } func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/authz/repository/eventsourcing/view/org.go b/internal/authz/repository/eventsourcing/view/org.go index fe9d6d51c2..f886123b2e 100644 --- a/internal/authz/repository/eventsourcing/view/org.go +++ b/internal/authz/repository/eventsourcing/view/org.go @@ -5,6 +5,7 @@ import ( org_view "github.com/caos/zitadel/internal/org/repository/view" org_model "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -19,12 +20,12 @@ func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, ui return org_view.SearchOrgs(v.Db, orgTable, req) } -func (v *View) PutOrg(org *org_model.OrgView) error { +func (v *View) PutOrg(org *org_model.OrgView, eventTimestamp time.Time) error { err := org_view.PutOrg(v.Db, orgTable, org) if err != nil { return err } - return v.ProcessedOrgSequence(org.Sequence) + return v.ProcessedOrgSequence(org.Sequence, eventTimestamp) } func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { @@ -39,6 +40,10 @@ func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgTable) } -func (v *View) ProcessedOrgSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgTable, eventSequence) +func (v *View) ProcessedOrgSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgTable) } diff --git a/internal/authz/repository/eventsourcing/view/sequence.go b/internal/authz/repository/eventsourcing/view/sequence.go index 815bcfebf8..ad628fbf7b 100644 --- a/internal/authz/repository/eventsourcing/view/sequence.go +++ b/internal/authz/repository/eventsourcing/view/sequence.go @@ -2,16 +2,29 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( sequencesTable = "authz.current_sequences" ) -func (v *View) saveCurrentSequence(viewName string, sequence uint64) error { - return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence) +func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimestamp time.Time) error { + return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimestamp) } func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) { return repository.LatestSequence(v.Db, sequencesTable, viewName) } + +func (v *View) updateSpoolerRunSequence(viewName string) error { + currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName) + if err != nil { + return err + } + if currentSequence.ViewName == "" { + currentSequence.ViewName = viewName + } + currentSequence.LastSuccessfulSpoolerRun = time.Now() + return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence) +} diff --git a/internal/authz/repository/eventsourcing/view/token.go b/internal/authz/repository/eventsourcing/view/token.go index 7c5a7163f6..bab80f238a 100644 --- a/internal/authz/repository/eventsourcing/view/token.go +++ b/internal/authz/repository/eventsourcing/view/token.go @@ -4,6 +4,7 @@ import ( usr_view "github.com/caos/zitadel/internal/user/repository/view" usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -14,36 +15,40 @@ func (v *View) TokenByID(tokenID string) (*usr_view_model.TokenView, error) { return usr_view.TokenByID(v.Db, tokenTable, tokenID) } -func (v *View) PutToken(token *usr_view_model.TokenView) error { +func (v *View) PutToken(token *usr_view_model.TokenView, eventTimestamp time.Time) error { err := usr_view.PutToken(v.Db, tokenTable, token) if err != nil { return err } - return v.ProcessedTokenSequence(token.Sequence) + return v.ProcessedTokenSequence(token.Sequence, eventTimestamp) } -func (v *View) DeleteToken(tokenID string, eventSequence uint64) error { +func (v *View) DeleteToken(tokenID string, eventSequence uint64, eventTimestamp time.Time) error { err := usr_view.DeleteToken(v.Db, tokenTable, tokenID) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error { +func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := usr_view.DeleteSessionTokens(v.Db, tokenTable, agentID, userID) if err != nil { return nil } - return v.ProcessedTokenSequence(eventSequence) + return v.ProcessedTokenSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestTokenSequence() (*repository.CurrentSequence, error) { return v.latestSequence(tokenTable) } -func (v *View) ProcessedTokenSequence(eventSequence uint64) error { - return v.saveCurrentSequence(tokenTable, eventSequence) +func (v *View) ProcessedTokenSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(tokenTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateTokenSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(tokenTable) } func (v *View) GetLatestTokenFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/authz/repository/eventsourcing/view/user_grant.go b/internal/authz/repository/eventsourcing/view/user_grant.go index cc44af9a75..68154dc3c9 100644 --- a/internal/authz/repository/eventsourcing/view/user_grant.go +++ b/internal/authz/repository/eventsourcing/view/user_grant.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/usergrant/repository/view" "github.com/caos/zitadel/internal/usergrant/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -31,28 +32,32 @@ func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([] return view.SearchUserGrants(v.Db, userGrantTable, request) } -func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error { +func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserGrant(v.Db, userGrantTable, grant) if err != nil { return err } - return v.ProcessedUserGrantSequence(sequence) + return v.ProcessedUserGrantSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error { +func (v *View) DeleteUserGrant(grantID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserGrant(v.Db, userGrantTable, grantID) if err != nil { return nil } - return v.ProcessedUserGrantSequence(eventSequence) + return v.ProcessedUserGrantSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserGrantSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userGrantTable) } -func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userGrantTable, eventSequence) +func (v *View) ProcessedUserGrantSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userGrantTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserGrantSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userGrantTable) } func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 0a5b46c781..c6b582b30d 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -23,6 +23,7 @@ type SystemDefaults struct { DomainVerification DomainVerification IamID string Notifications Notifications + WebAuthN WebAuthN } type ZitadelDocs struct { @@ -52,7 +53,7 @@ type OTPConfig struct { type VerificationLifetimes struct { PasswordCheck types.Duration ExternalLoginCheck types.Duration - MfaInitSkip types.Duration + MFAInitSkip types.Duration SecondFactorCheck types.Duration MultiFactorCheck types.Duration } @@ -89,3 +90,9 @@ type TemplateData struct { VerifyPhone templates.TemplateData DomainClaimed templates.TemplateData } + +type WebAuthN struct { + ID string + Origin string + DisplayName string +} diff --git a/internal/eventstore/internal/repository/sql/filter.go b/internal/eventstore/internal/repository/sql/filter.go index 0b393f885f..e9a98939da 100644 --- a/internal/eventstore/internal/repository/sql/filter.go +++ b/internal/eventstore/internal/repository/sql/filter.go @@ -8,7 +8,7 @@ import ( "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type Querier interface { diff --git a/internal/eventstore/internal/repository/sql/push.go b/internal/eventstore/internal/repository/sql/push.go index b6af36cce0..3a3802ebf2 100644 --- a/internal/eventstore/internal/repository/sql/push.go +++ b/internal/eventstore/internal/repository/sql/push.go @@ -8,7 +8,7 @@ import ( "github.com/caos/logging" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/cockroachdb/cockroach-go/v2/crdb" ) diff --git a/internal/eventstore/query/handler.go b/internal/eventstore/query/handler.go index 8effb168c0..36bba2547e 100755 --- a/internal/eventstore/query/handler.go +++ b/internal/eventstore/query/handler.go @@ -11,6 +11,7 @@ type Handler interface { EventQuery() (*models.SearchQuery, error) Reduce(*models.Event) error OnError(event *models.Event, err error) error + OnSuccess() error MinimumCycleDuration() time.Duration QueryLimit() uint64 } diff --git a/internal/eventstore/spooler/config.go b/internal/eventstore/spooler/config.go index fbad7d0496..fa796f06c3 100644 --- a/internal/eventstore/spooler/config.go +++ b/internal/eventstore/spooler/config.go @@ -28,7 +28,7 @@ func (c *Config) New() *Spooler { lockID: lockID, eventstore: c.Eventstore, locker: c.Locker, - queue: make(chan *spooledHandler), + queue: make(chan *spooledHandler, len(c.ViewHandlers)), workers: c.ConcurrentWorkers, } } diff --git a/internal/eventstore/spooler/spooler.go b/internal/eventstore/spooler/spooler.go index d95f45ad4a..fb480f7916 100644 --- a/internal/eventstore/spooler/spooler.go +++ b/internal/eventstore/spooler/spooler.go @@ -9,7 +9,7 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/query" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/view/repository" "time" @@ -52,8 +52,7 @@ func (s *Spooler) Start() { } go func() { for _, handler := range s.handlers { - handler := &spooledHandler{Handler: handler, locker: s.locker, queuedAt: time.Now(), eventstore: s.eventstore} - s.queue <- handler + s.queue <- &spooledHandler{Handler: handler, locker: s.locker, queuedAt: time.Now(), eventstore: s.eventstore} } }() } @@ -79,6 +78,7 @@ func (s *spooledHandler) load(workerID string) { errs <- s.process(ctx, events, workerID) logging.Log("SPOOL-0pV8o").WithField("view", s.ViewModel()).WithField("worker", workerID).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("process done") } + } <-ctx.Done() } @@ -103,7 +103,9 @@ func (s *spooledHandler) process(ctx context.Context, events []*models.Event, wo } } } - return nil + err := s.OnSuccess() + logging.LogWithFields("SPOOL-49ods", "view", s.ViewModel(), "worker", workerID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Warn("could not process on success func") + return err } func (s *spooledHandler) query(ctx context.Context) ([]*models.Event, error) { @@ -165,7 +167,7 @@ func (s *spooledHandler) lock(ctx context.Context, errs chan<- error, workerID s func HandleError(event *models.Event, failedErr error, latestFailedEvent func(sequence uint64) (*repository.FailedEvent, error), processFailedEvent func(*repository.FailedEvent) error, - processSequence func(uint64) error, errorCountUntilSkip uint64) error { + processSequence func(uint64, time.Time) error, errorCountUntilSkip uint64) error { failedEvent, err := latestFailedEvent(event.Sequence) if err != nil { return err @@ -177,7 +179,11 @@ func HandleError(event *models.Event, failedErr error, return err } if errorCountUntilSkip <= failedEvent.FailureCount { - return processSequence(event.Sequence) + return processSequence(event.Sequence, event.CreationDate) } return nil } + +func HandleSuccess(updateSpoolerRunTimestamp func() error) error { + return updateSpoolerRunTimestamp() +} diff --git a/internal/eventstore/spooler/spooler_test.go b/internal/eventstore/spooler/spooler_test.go index 7a852ed871..721ad74e26 100644 --- a/internal/eventstore/spooler/spooler_test.go +++ b/internal/eventstore/spooler/spooler_test.go @@ -40,6 +40,9 @@ func (h *testHandler) Reduce(*models.Event) error { func (h *testHandler) OnError(event *models.Event, err error) error { return err } +func (h *testHandler) OnSuccess() error { + return nil +} func (h *testHandler) MinimumCycleDuration() time.Duration { return h.cycleDuration } @@ -429,7 +432,7 @@ func TestHandleError(t *testing.T) { func(*repository.FailedEvent) error { return nil }, - func(uint64) error { + func(uint64, time.Time) error { processedSequence = true return nil }, diff --git a/internal/iam/model/iam.go b/internal/iam/model/iam.go index 09610a5c89..f80ea3b400 100644 --- a/internal/iam/model/iam.go +++ b/internal/iam/model/iam.go @@ -14,6 +14,7 @@ const ( Step5 Step6 Step7 + Step8 //StepCount marks the the length of possible steps (StepCount-1 == last possible step) StepCount ) diff --git a/internal/iam/model/login_policy.go b/internal/iam/model/login_policy.go index 265391b5d8..46751498d9 100644 --- a/internal/iam/model/login_policy.go +++ b/internal/iam/model/login_policy.go @@ -16,6 +16,7 @@ type LoginPolicy struct { ForceMFA bool SecondFactors []SecondFactorType MultiFactors []MultiFactorType + PasswordlessType PasswordlessType } type IDPProvider struct { @@ -53,6 +54,13 @@ const ( MultiFactorTypeU2FWithPIN ) +type PasswordlessType int32 + +const ( + PasswordlessTypeNotAllowed PasswordlessType = iota + PasswordlessTypeAllowed +) + func (p *LoginPolicy) IsValid() bool { return p.ObjectRoot.AggregateID != "" } diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go index c41649984d..4bc6d51850 100644 --- a/internal/iam/model/login_policy_view.go +++ b/internal/iam/model/login_policy_view.go @@ -11,6 +11,7 @@ type LoginPolicyView struct { AllowRegister bool AllowExternalIDP bool ForceMFA bool + PasswordlessType PasswordlessType SecondFactors []SecondFactorType MultiFactors []MultiFactorType Default bool diff --git a/internal/iam/repository/eventsourcing/eventstore.go b/internal/iam/repository/eventsourcing/eventstore.go index c2fad65924..e1ed4d2d26 100644 --- a/internal/iam/repository/eventsourcing/eventstore.go +++ b/internal/iam/repository/eventsourcing/eventstore.go @@ -14,7 +14,7 @@ import ( iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" "github.com/caos/zitadel/internal/id" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" ) type IAMEventstore struct { diff --git a/internal/iam/repository/eventsourcing/eventstore_mock_test.go b/internal/iam/repository/eventsourcing/eventstore_mock_test.go index 584e320c45..e41a323014 100644 --- a/internal/iam/repository/eventsourcing/eventstore_mock_test.go +++ b/internal/iam/repository/eventsourcing/eventstore_mock_test.go @@ -127,8 +127,8 @@ func GetMockManipulateIAMWithLoginPolicy(ctrl *gomock.Controller) *IAMEventstore func GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl *gomock.Controller) *IAMEventstore { policyData, _ := json.Marshal(model.LoginPolicy{AllowRegister: true, AllowUsernamePassword: true, AllowExternalIdp: true}) idpProviderData, _ := json.Marshal(model.IDPProvider{IDPConfigID: "IDPConfigID", Type: 1}) - secondFactor, _ := json.Marshal(model.MFA{MfaType: int32(model2.SecondFactorTypeOTP)}) - multiFactor, _ := json.Marshal(model.MFA{MfaType: int32(model2.MultiFactorTypeU2FWithPIN)}) + secondFactor, _ := json.Marshal(model.MFA{MFAType: int32(model2.SecondFactorTypeOTP)}) + multiFactor, _ := json.Marshal(model.MFA{MFAType: int32(model2.MultiFactorTypeU2FWithPIN)}) events := []*es_models.Event{ {AggregateID: "AggregateID", Sequence: 1, Type: model.IAMSetupStarted}, {AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicyAdded, Data: policyData}, diff --git a/internal/iam/repository/eventsourcing/iam.go b/internal/iam/repository/eventsourcing/iam.go index 2522316602..ba62d70c52 100644 --- a/internal/iam/repository/eventsourcing/iam.go +++ b/internal/iam/repository/eventsourcing/iam.go @@ -359,7 +359,7 @@ func LoginPolicySecondFactorAddedAggregate(aggCreator *es_models.AggregateCreato AggregateTypeFilter(model.IAMAggregate). AggregateIDFilter(existing.AggregateID) - validation := checkExistingLoginPolicySecondFactorValidation(mfa.MfaType) + validation := checkExistingLoginPolicySecondFactorValidation(mfa.MFAType) agg.SetPrecondition(validationQuery, validation) return agg.AppendEvent(model.LoginPolicySecondFactorAdded, mfa) } @@ -391,7 +391,7 @@ func LoginPolicyMultiFactorAddedAggregate(aggCreator *es_models.AggregateCreator AggregateTypeFilter(model.IAMAggregate). AggregateIDFilter(existing.AggregateID) - validation := checkExistingLoginPolicyMultiFactorValidation(mfa.MfaType) + validation := checkExistingLoginPolicyMultiFactorValidation(mfa.MFAType) agg.SetPrecondition(validationQuery, validation) return agg.AppendEvent(model.LoginPolicyMultiFactorAdded, mfa) } @@ -689,7 +689,7 @@ func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_m if err != nil { return err } - mfas = append(mfas, idp.MfaType) + mfas = append(mfas, idp.MFAType) case model.LoginPolicySecondFactorRemoved: mfa := new(model.MFA) err := mfa.SetData(event) @@ -697,7 +697,7 @@ func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_m return err } for i := len(mfas) - 1; i >= 0; i-- { - if mfas[i] == mfa.MfaType { + if mfas[i] == mfa.MFAType { mfas[i] = mfas[len(mfas)-1] mfas[len(mfas)-1] = 0 mfas = mfas[:len(mfas)-1] @@ -726,7 +726,7 @@ func checkExistingLoginPolicyMultiFactorValidation(mfaType int32) func(...*es_mo if err != nil { return err } - mfas = append(mfas, idp.MfaType) + mfas = append(mfas, idp.MFAType) case model.LoginPolicyMultiFactorRemoved: mfa := new(model.MFA) err := mfa.SetData(event) @@ -734,7 +734,7 @@ func checkExistingLoginPolicyMultiFactorValidation(mfaType int32) func(...*es_mo return err } for i := len(mfas) - 1; i >= 0; i-- { - if mfas[i] == mfa.MfaType { + if mfas[i] == mfa.MFAType { mfas[i] = mfas[len(mfas)-1] mfas[len(mfas)-1] = 0 mfas = mfas[:len(mfas)-1] diff --git a/internal/iam/repository/eventsourcing/iam_test.go b/internal/iam/repository/eventsourcing/iam_test.go index 748c27ff81..bf3625dc8c 100644 --- a/internal/iam/repository/eventsourcing/iam_test.go +++ b/internal/iam/repository/eventsourcing/iam_test.go @@ -1497,7 +1497,7 @@ func TestLoginPolicySecondFactorAddedAggregate(t *testing.T) { AllowUsernamePassword: true, }}, newMFA: &model.MFA{ - MfaType: int32(iam_model.SecondFactorTypeOTP), + MFAType: int32(iam_model.SecondFactorTypeOTP), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -1587,7 +1587,7 @@ func TestLoginPolicySecondFactorRemovedAggregate(t *testing.T) { }, }}, mfa: &model.MFA{ - MfaType: int32(iam_model.SecondFactorTypeOTP), + MFAType: int32(iam_model.SecondFactorTypeOTP), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -1674,7 +1674,7 @@ func TestLoginPolicyMultiFactorAddedAggregate(t *testing.T) { AllowUsernamePassword: true, }}, newMFA: &model.MFA{ - MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN), + MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -1764,7 +1764,7 @@ func TestLoginPolicyMultiFactorRemovedAggregate(t *testing.T) { }, }}, mfa: &model.MFA{ - MfaType: int32(iam_model.SecondFactorTypeOTP), + MFAType: int32(iam_model.SecondFactorTypeOTP), }, aggCreator: models.NewAggregateCreator("Test"), }, diff --git a/internal/iam/repository/eventsourcing/model/login_policy.go b/internal/iam/repository/eventsourcing/model/login_policy.go index 6421a86747..7b28b18b96 100644 --- a/internal/iam/repository/eventsourcing/model/login_policy.go +++ b/internal/iam/repository/eventsourcing/model/login_policy.go @@ -14,7 +14,8 @@ type LoginPolicy struct { AllowUsernamePassword bool `json:"allowUsernamePassword"` AllowRegister bool `json:"allowRegister"` AllowExternalIdp bool `json:"allowExternalIdp"` - ForceMFA bool `json:"forceMfa"` + ForceMFA bool `json:"forceMFA"` + PasswordlessType int32 `json:"passwordlessType"` IDPProviders []*IDPProvider `json:"-"` SecondFactors []int32 `json:"-"` MultiFactors []int32 `json:"-"` @@ -31,7 +32,7 @@ type IDPProviderID struct { } type MFA struct { - MfaType int32 `json:"mfaType"` + MFAType int32 `json:"mfaType"` } func GetIDPProvider(providers []*IDPProvider, id string) (int, *IDPProvider) { @@ -65,6 +66,7 @@ func LoginPolicyToModel(policy *LoginPolicy) *iam_model.LoginPolicy { ForceMFA: policy.ForceMFA, SecondFactors: secondFactors, MultiFactors: multiFactors, + PasswordlessType: iam_model.PasswordlessType(policy.PasswordlessType), } } @@ -82,6 +84,7 @@ func LoginPolicyFromModel(policy *iam_model.LoginPolicy) *LoginPolicy { ForceMFA: policy.ForceMFA, SecondFactors: secondFactors, MultiFactors: multiFactors, + PasswordlessType: int32(policy.PasswordlessType), } } @@ -126,7 +129,7 @@ func SecondFactorsFromModel(mfas []iam_model.SecondFactorType) []int32 { } func SecondFactorFromModel(mfa iam_model.SecondFactorType) *MFA { - return &MFA{MfaType: int32(mfa)} + return &MFA{MFAType: int32(mfa)} } func SecondFactorsToModel(mfas []int32) []iam_model.SecondFactorType { @@ -146,7 +149,7 @@ func MultiFactorsFromModel(mfas []iam_model.MultiFactorType) []int32 { } func MultiFactorFromModel(mfa iam_model.MultiFactorType) *MFA { - return &MFA{MfaType: int32(mfa)} + return &MFA{MFAType: int32(mfa)} } func MultiFactorsToModel(mfas []int32) []iam_model.MultiFactorType { @@ -172,6 +175,9 @@ func (p *LoginPolicy) Changes(changed *LoginPolicy) map[string]interface{} { if changed.ForceMFA != p.ForceMFA { changes["forceMFA"] = changed.ForceMFA } + if changed.PasswordlessType != p.PasswordlessType { + changes["passwordlessType"] = changed.PasswordlessType + } return changes } @@ -221,7 +227,7 @@ func (iam *IAM) appendAddSecondFactorToLoginPolicyEvent(event *es_models.Event) if err != nil { return err } - iam.DefaultLoginPolicy.SecondFactors = append(iam.DefaultLoginPolicy.SecondFactors, mfa.MfaType) + iam.DefaultLoginPolicy.SecondFactors = append(iam.DefaultLoginPolicy.SecondFactors, mfa.MFAType) return nil } @@ -231,7 +237,7 @@ func (iam *IAM) appendRemoveSecondFactorFromLoginPolicyEvent(event *es_models.Ev if err != nil { return err } - if i, m := GetMFA(iam.DefaultLoginPolicy.SecondFactors, mfa.MfaType); m != 0 { + if i, m := GetMFA(iam.DefaultLoginPolicy.SecondFactors, mfa.MFAType); m != 0 { iam.DefaultLoginPolicy.SecondFactors[i] = iam.DefaultLoginPolicy.SecondFactors[len(iam.DefaultLoginPolicy.SecondFactors)-1] iam.DefaultLoginPolicy.SecondFactors[len(iam.DefaultLoginPolicy.SecondFactors)-1] = 0 iam.DefaultLoginPolicy.SecondFactors = iam.DefaultLoginPolicy.SecondFactors[:len(iam.DefaultLoginPolicy.SecondFactors)-1] @@ -246,7 +252,7 @@ func (iam *IAM) appendAddMultiFactorToLoginPolicyEvent(event *es_models.Event) e if err != nil { return err } - iam.DefaultLoginPolicy.MultiFactors = append(iam.DefaultLoginPolicy.MultiFactors, mfa.MfaType) + iam.DefaultLoginPolicy.MultiFactors = append(iam.DefaultLoginPolicy.MultiFactors, mfa.MFAType) return nil } @@ -256,7 +262,7 @@ func (iam *IAM) appendRemoveMultiFactorFromLoginPolicyEvent(event *es_models.Eve if err != nil { return err } - if i, m := GetMFA(iam.DefaultLoginPolicy.MultiFactors, mfa.MfaType); m != 0 { + if i, m := GetMFA(iam.DefaultLoginPolicy.MultiFactors, mfa.MFAType); m != 0 { iam.DefaultLoginPolicy.MultiFactors[i] = iam.DefaultLoginPolicy.MultiFactors[len(iam.DefaultLoginPolicy.MultiFactors)-1] iam.DefaultLoginPolicy.MultiFactors[len(iam.DefaultLoginPolicy.MultiFactors)-1] = 0 iam.DefaultLoginPolicy.MultiFactors = iam.DefaultLoginPolicy.MultiFactors[:len(iam.DefaultLoginPolicy.MultiFactors)-1] diff --git a/internal/iam/repository/eventsourcing/model/login_policy_test.go b/internal/iam/repository/eventsourcing/model/login_policy_test.go index cd39fa07e8..a7a9164885 100644 --- a/internal/iam/repository/eventsourcing/model/login_policy_test.go +++ b/internal/iam/repository/eventsourcing/model/login_policy_test.go @@ -275,7 +275,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { name: "append add second factor to login policy event", args: args{ iam: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}}, - mfa: &MFA{MfaType: int32(model.SecondFactorTypeOTP)}, + mfa: &MFA{MFAType: int32(model.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ @@ -294,7 +294,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { if len(tt.result.DefaultLoginPolicy.SecondFactors) != len(tt.args.iam.DefaultLoginPolicy.SecondFactors) { t.Errorf("got wrong second factors len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.SecondFactors), len(tt.args.iam.DefaultLoginPolicy.SecondFactors)) } - if tt.result.DefaultLoginPolicy.SecondFactors[0] != tt.args.mfa.MfaType { + if tt.result.DefaultLoginPolicy.SecondFactors[0] != tt.args.mfa.MFAType { t.Errorf("got wrong second factor: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.SecondFactors[0], tt.args.mfa) } }) @@ -320,7 +320,7 @@ func TestRemoveSecondFactorToPolicyEvent(t *testing.T) { SecondFactors: []int32{ int32(model.SecondFactorTypeOTP), }}}, - mfa: &MFA{MfaType: int32(model.SecondFactorTypeOTP)}, + mfa: &MFA{MFAType: int32(model.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ @@ -359,7 +359,7 @@ func TestAppendAddMultiFactorToPolicyEvent(t *testing.T) { name: "append add mfa to login policy event", args: args{ iam: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}}, - mfa: &MFA{MfaType: int32(model.MultiFactorTypeU2FWithPIN)}, + mfa: &MFA{MFAType: int32(model.MultiFactorTypeU2FWithPIN)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ @@ -378,7 +378,7 @@ func TestAppendAddMultiFactorToPolicyEvent(t *testing.T) { if len(tt.result.DefaultLoginPolicy.MultiFactors) != len(tt.args.iam.DefaultLoginPolicy.MultiFactors) { t.Errorf("got wrong mfas len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.MultiFactors), len(tt.args.iam.DefaultLoginPolicy.MultiFactors)) } - if tt.result.DefaultLoginPolicy.MultiFactors[0] != tt.args.mfa.MfaType { + if tt.result.DefaultLoginPolicy.MultiFactors[0] != tt.args.mfa.MFAType { t.Errorf("got wrong mfa: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.MultiFactors[0], tt.args.mfa) } }) @@ -404,7 +404,7 @@ func TestRemoveMultiFactorToPolicyEvent(t *testing.T) { MultiFactors: []int32{ int32(model.MultiFactorTypeU2FWithPIN), }}}, - mfa: &MFA{MfaType: int32(model.MultiFactorTypeU2FWithPIN)}, + mfa: &MFA{MFAType: int32(model.MultiFactorTypeU2FWithPIN)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ diff --git a/internal/iam/repository/view/model/login_policy.go b/internal/iam/repository/view/model/login_policy.go index 9977c637bd..7bdc919a87 100644 --- a/internal/iam/repository/view/model/login_policy.go +++ b/internal/iam/repository/view/model/login_policy.go @@ -28,6 +28,7 @@ type LoginPolicyView struct { AllowUsernamePassword bool `json:"allowUsernamePassword" gorm:"column:allow_username_password"` AllowExternalIDP bool `json:"allowExternalIdp" gorm:"column:allow_external_idp"` ForceMFA bool `json:"forceMFA" gorm:"column:force_mfa"` + PasswordlessType int32 `json:"passwordlessType" gorm:"column:passwordless_type"` SecondFactors pq.Int64Array `json:"-" gorm:"column:second_factors"` MultiFactors pq.Int64Array `json:"-" gorm:"column:multi_factors"` Default bool `json:"-" gorm:"-"` @@ -45,6 +46,7 @@ func LoginPolicyViewFromModel(policy *model.LoginPolicyView) *LoginPolicyView { AllowExternalIDP: policy.AllowExternalIDP, AllowUsernamePassword: policy.AllowUsernamePassword, ForceMFA: policy.ForceMFA, + PasswordlessType: int32(policy.PasswordlessType), SecondFactors: secondFactorsFromModel(policy.SecondFactors), MultiFactors: multiFactorsFromModel(policy.MultiFactors), Default: policy.Default, @@ -77,6 +79,7 @@ func LoginPolicyViewToModel(policy *LoginPolicyView) *model.LoginPolicyView { AllowExternalIDP: policy.AllowExternalIDP, AllowUsernamePassword: policy.AllowUsernamePassword, ForceMFA: policy.ForceMFA, + PasswordlessType: model.PasswordlessType(policy.PasswordlessType), SecondFactors: secondFactorsToModel(policy.SecondFactors), MultiFactors: multiFactorsToToModel(policy.MultiFactors), Default: policy.Default, @@ -115,7 +118,7 @@ func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) { if err != nil { return err } - p.SecondFactors = append(p.SecondFactors, int64(mfa.MfaType)) + p.SecondFactors = append(p.SecondFactors, int64(mfa.MFAType)) case es_model.LoginPolicySecondFactorRemoved, org_es_model.LoginPolicySecondFactorRemoved: err = p.removeSecondFactor(event) case es_model.LoginPolicyMultiFactorAdded, org_es_model.LoginPolicyMultiFactorAdded: @@ -124,7 +127,7 @@ func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) { if err != nil { return err } - p.MultiFactors = append(p.MultiFactors, int64(mfa.MfaType)) + p.MultiFactors = append(p.MultiFactors, int64(mfa.MFAType)) case es_model.LoginPolicyMultiFactorRemoved, org_es_model.LoginPolicyMultiFactorRemoved: err = p.removeMultiFactor(event) } @@ -150,7 +153,7 @@ func (p *LoginPolicyView) removeSecondFactor(event *models.Event) error { return err } for i := len(p.SecondFactors) - 1; i >= 0; i-- { - if p.SecondFactors[i] == int64(mfa.MfaType) { + if p.SecondFactors[i] == int64(mfa.MFAType) { copy(p.SecondFactors[i:], p.SecondFactors[i+1:]) p.SecondFactors[len(p.SecondFactors)-1] = 0 p.SecondFactors = p.SecondFactors[:len(p.SecondFactors)-1] @@ -167,7 +170,7 @@ func (p *LoginPolicyView) removeMultiFactor(event *models.Event) error { return err } for i := len(p.MultiFactors) - 1; i >= 0; i-- { - if p.MultiFactors[i] == int64(mfa.MfaType) { + if p.MultiFactors[i] == int64(mfa.MFAType) { copy(p.MultiFactors[i:], p.MultiFactors[i+1:]) p.MultiFactors[len(p.MultiFactors)-1] = 0 p.MultiFactors = p.MultiFactors[:len(p.MultiFactors)-1] diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go index ffb6335a28..6bc11af854 100644 --- a/internal/management/repository/eventsourcing/eventstore/org.go +++ b/internal/management/repository/eventsourcing/eventstore/org.go @@ -22,7 +22,7 @@ import ( org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing" ) @@ -122,7 +122,7 @@ func (repo *OrgRepository) SearchMyOrgDomains(ctx context.Context, request *org_ } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -219,7 +219,7 @@ func (repo *OrgRepository) SearchMyOrgMembers(ctx context.Context, request *org_ } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -306,7 +306,7 @@ func (repo *OrgRepository) SearchIDPConfigs(ctx context.Context, request *iam_mo } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -414,9 +414,9 @@ func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_ request.AppendAggregateIDQuery(authz.GetCtxData(ctx).OrgID) } request.EnsureLimit(repo.SearchLimit) - sequence, sequenceErr := repo.View.GetLatestIdpProviderSequence() + sequence, sequenceErr := repo.View.GetLatestIDPProviderSequence() logging.Log("EVENT-Tuiks").OnError(sequenceErr).Warn("could not read latest iam sequence") - providers, count, err := repo.View.SearchIdpProviders(request) + providers, count, err := repo.View.SearchIDPProviders(request) if err != nil { return nil, err } @@ -428,7 +428,7 @@ func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_ } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/management/repository/eventsourcing/eventstore/project.go b/internal/management/repository/eventsourcing/eventstore/project.go index 432bee10dc..5a9343ff11 100644 --- a/internal/management/repository/eventsourcing/eventstore/project.go +++ b/internal/management/repository/eventsourcing/eventstore/project.go @@ -141,7 +141,7 @@ func (repo *ProjectRepo) SearchProjects(ctx context.Context, request *proj_model } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -162,7 +162,7 @@ func (repo *ProjectRepo) SearchProjects(ctx context.Context, request *proj_model } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -212,7 +212,7 @@ func (repo *ProjectRepo) SearchProjectMembers(ctx context.Context, request *proj } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -285,7 +285,7 @@ func (repo *ProjectRepo) SearchProjectRoles(ctx context.Context, projectID strin } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -380,7 +380,7 @@ func (repo *ProjectRepo) SearchApplications(ctx context.Context, request *proj_m } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -437,7 +437,7 @@ func (repo *ProjectRepo) SearchProjectGrants(ctx context.Context, request *proj_ } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -467,7 +467,7 @@ func (repo *ProjectRepo) SearchGrantedProjects(ctx context.Context, request *pro } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -488,7 +488,7 @@ func (repo *ProjectRepo) SearchGrantedProjects(ctx context.Context, request *pro } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -626,7 +626,7 @@ func (repo *ProjectRepo) SearchProjectGrantMembers(ctx context.Context, request } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 3ab8118411..3be494fd68 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -172,7 +172,7 @@ func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSe } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -209,7 +209,7 @@ func (repo *UserRepo) IsUserUnique(ctx context.Context, userName, email string) return repo.View.IsUserUnique(userName, email) } -func (repo *UserRepo) UserMfas(ctx context.Context, userID string) ([]*usr_model.MultiFactor, error) { +func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model.MultiFactor, error) { user, err := repo.UserByID(ctx, userID) if err != nil { return nil, err @@ -217,10 +217,10 @@ func (repo *UserRepo) UserMfas(ctx context.Context, userID string) ([]*usr_model if user.HumanView == nil { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-xx0hV", "Errors.User.NotHuman") } - if user.OTPState == usr_model.MfaStateUnspecified { + if user.OTPState == usr_model.MFAStateUnspecified { return []*usr_model.MultiFactor{}, nil } - return []*usr_model.MultiFactor{{Type: usr_model.MfaTypeOTP, State: user.OTPState}}, nil + return []*usr_model.MultiFactor{{Type: usr_model.MFATypeOTP, State: user.OTPState}}, nil } func (repo *UserRepo) RemoveOTP(ctx context.Context, userID string) error { @@ -274,7 +274,7 @@ func (repo *UserRepo) SearchExternalIDPs(ctx context.Context, request *usr_model } if seqErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -311,7 +311,7 @@ func (repo *UserRepo) SearchMachineKeys(ctx context.Context, request *usr_model. } if seqErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -419,7 +419,7 @@ func (repo *UserRepo) SearchUserMemberships(ctx context.Context, request *usr_mo } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -459,7 +459,7 @@ func handleSearchUserMembershipsPermissions(ctx context.Context, request *usr_mo } if sequence != nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result } diff --git a/internal/management/repository/eventsourcing/eventstore/user_grant.go b/internal/management/repository/eventsourcing/eventstore/user_grant.go index ddcfedb81d..09568b64fc 100644 --- a/internal/management/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/management/repository/eventsourcing/eventstore/user_grant.go @@ -137,7 +137,7 @@ func (repo *UserGrantRepo) SearchUserGrants(ctx context.Context, request *grant_ } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result, nil } @@ -183,7 +183,7 @@ func checkContainsPermID(ids []string, query *grant_model.UserGrantSearchQuery, } if sequence != nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.CurrentTimestamp + result.Timestamp = sequence.LastSuccessfulSpoolerRun } return result } diff --git a/internal/management/repository/eventsourcing/handler/application.go b/internal/management/repository/eventsourcing/handler/application.go index 4395737197..8d1490c433 100644 --- a/internal/management/repository/eventsourcing/handler/application.go +++ b/internal/management/repository/eventsourcing/handler/application.go @@ -65,33 +65,37 @@ func (a *Application) Reduce(event *models.Event) (err error) { if err != nil { return err } - return a.view.DeleteApplication(app.ID, event.Sequence) + return a.view.DeleteApplication(app.ID, event.Sequence, event.CreationDate) case es_model.ProjectChanged: apps, err := a.view.ApplicationsByProjectID(event.AggregateID) if err != nil { return err } if len(apps) == 0 { - return a.view.ProcessedApplicationSequence(event.Sequence) + return a.view.ProcessedApplicationSequence(event.Sequence, event.CreationDate) } for _, app := range apps { if err := app.AppendEvent(event); err != nil { return err } } - return a.view.PutApplications(apps, event.Sequence) + return a.view.PutApplications(apps, event.Sequence, event.CreationDate) case es_model.ProjectRemoved: return a.view.DeleteApplicationsByProjectID(event.AggregateID) default: - return a.view.ProcessedApplicationSequence(event.Sequence) + return a.view.ProcessedApplicationSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return a.view.PutApplication(app) + return a.view.PutApplication(app, event.CreationDate) } func (a *Application) OnError(event *models.Event, spoolerError error) error { logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler") return spooler.HandleError(event, spoolerError, a.view.GetLatestApplicationFailedEvent, a.view.ProcessedApplicationFailedEvent, a.view.ProcessedApplicationSequence, a.errorCountUntilSkip) } + +func (a *Application) OnSuccess() error { + return spooler.HandleSuccess(a.view.UpdateApplicationSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/idp_config.go b/internal/management/repository/eventsourcing/handler/idp_config.go index 7ce0e6c487..07301c71b8 100644 --- a/internal/management/repository/eventsourcing/handler/idp_config.go +++ b/internal/management/repository/eventsourcing/handler/idp_config.go @@ -66,17 +66,21 @@ func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve if err != nil { return err } - return m.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence) + return m.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIDPConfigSequence(event.Sequence) + return m.view.ProcessedIDPConfigSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIDPConfig(idp, idp.Sequence) + return m.view.PutIDPConfig(idp, idp.Sequence, event.CreationDate) } -func (m *IDPConfig) OnError(event *models.Event, err error) error { +func (i *IDPConfig) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Nxu8s", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler") - return spooler.HandleError(event, err, m.view.GetLatestIDPConfigFailedEvent, m.view.ProcessedIDPConfigFailedEvent, m.view.ProcessedIDPConfigSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestIDPConfigFailedEvent, i.view.ProcessedIDPConfigFailedEvent, i.view.ProcessedIDPConfigSequence, i.errorCountUntilSkip) +} + +func (i *IDPConfig) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateIDPConfigSpoolerRunTimestamp) } diff --git a/internal/management/repository/eventsourcing/handler/idp_providers.go b/internal/management/repository/eventsourcing/handler/idp_providers.go index 56e8fbc7d2..9d873eed67 100644 --- a/internal/management/repository/eventsourcing/handler/idp_providers.go +++ b/internal/management/repository/eventsourcing/handler/idp_providers.go @@ -32,7 +32,7 @@ func (m *IDPProvider) ViewModel() string { } func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestIdpProviderSequence() + sequence, err := m.view.GetLatestIDPProviderSequence() if err != nil { return nil, err } @@ -64,7 +64,7 @@ func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { if err != nil { return err } - return m.view.DeleteIdpProvider(event.AggregateID, provider.IDPConfigID, event.Sequence) + return m.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence, event.CreationDate) case model.IDPConfigChanged, org_es_model.IDPConfigChanged: esConfig := new(iam_view_model.IDPConfigView) providerType := iam_model.IDPProviderTypeSystem @@ -72,7 +72,7 @@ func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { providerType = iam_model.IDPProviderTypeOrg } esConfig.AppendEvent(providerType, event) - providers, err := m.view.IdpProvidersByIdpConfigID(event.AggregateID, esConfig.IDPConfigID) + providers, err := m.view.IDPProvidersByIdpConfigID(event.AggregateID, esConfig.IDPConfigID) if err != nil { return err } @@ -88,16 +88,16 @@ func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) { for _, provider := range providers { m.fillConfigData(provider, config) } - return m.view.PutIdpProviders(event.Sequence, providers...) + return m.view.PutIDPProviders(event.Sequence, event.CreationDate, providers...) case org_es_model.LoginPolicyRemoved: - return m.view.DeleteIdpProvidersByAggregateID(event.AggregateID, event.Sequence) + return m.view.DeleteIDPProvidersByAggregateID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedIdpProviderSequence(event.Sequence) + return m.view.ProcessedIDPProviderSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutIdpProvider(provider, provider.Sequence) + return m.view.PutIDPProvider(provider, provider.Sequence, event.CreationDate) } func (m *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { @@ -123,5 +123,9 @@ func (m *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, c func (m *IDPProvider) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Msj8c", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestIdpProviderFailedEvent, m.view.ProcessedIdpProviderFailedEvent, m.view.ProcessedIdpProviderSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, m.view.GetLatestIDPProviderFailedEvent, m.view.ProcessedIDPProviderFailedEvent, m.view.ProcessedIDPProviderSequence, m.errorCountUntilSkip) +} + +func (m *IDPProvider) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateIDPProviderSpoolerRunTimestamp) } diff --git a/internal/management/repository/eventsourcing/handler/label_policy.go b/internal/management/repository/eventsourcing/handler/label_policy.go index 8c3238159f..2c47a03c80 100644 --- a/internal/management/repository/eventsourcing/handler/label_policy.go +++ b/internal/management/repository/eventsourcing/handler/label_policy.go @@ -53,15 +53,19 @@ func (m *LabelPolicy) processLabelPolicy(event *models.Event) (err error) { } err = policy.AppendEvent(event) default: - return m.view.ProcessedLabelPolicySequence(event.Sequence) + return m.view.ProcessedLabelPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutLabelPolicy(policy, policy.Sequence) + return m.view.PutLabelPolicy(policy, policy.Sequence, event.CreationDate) } func (m *LabelPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler") return spooler.HandleError(event, err, m.view.GetLatestLabelPolicyFailedEvent, m.view.ProcessedLabelPolicyFailedEvent, m.view.ProcessedLabelPolicySequence, m.errorCountUntilSkip) } + +func (m *LabelPolicy) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateLabelPolicySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/login_policy.go b/internal/management/repository/eventsourcing/handler/login_policy.go index 305953a7b7..cb6f06c4d2 100644 --- a/internal/management/repository/eventsourcing/handler/login_policy.go +++ b/internal/management/repository/eventsourcing/handler/login_policy.go @@ -57,17 +57,21 @@ func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { } err = policy.AppendEvent(event) case model.LoginPolicyRemoved: - return m.view.DeleteLoginPolicy(event.AggregateID, event.Sequence) + return m.view.DeleteLoginPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedLoginPolicySequence(event.Sequence) + return m.view.ProcessedLoginPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutLoginPolicy(policy, policy.Sequence) + return m.view.PutLoginPolicy(policy, policy.Sequence, event.CreationDate) } func (m *LoginPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") return spooler.HandleError(event, err, m.view.GetLatestLoginPolicyFailedEvent, m.view.ProcessedLoginPolicyFailedEvent, m.view.ProcessedLoginPolicySequence, m.errorCountUntilSkip) } + +func (m *LoginPolicy) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateLoginPolicySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/machine_keys.go b/internal/management/repository/eventsourcing/handler/machine_keys.go index c09a2bdcf7..cd6b29b6a8 100644 --- a/internal/management/repository/eventsourcing/handler/machine_keys.go +++ b/internal/management/repository/eventsourcing/handler/machine_keys.go @@ -48,26 +48,30 @@ func (d *MachineKeys) processMachineKeys(event *models.Event) (err error) { case model.MachineKeyAdded: err = key.AppendEvent(event) if key.ExpirationDate.Before(time.Now()) { - return d.view.ProcessedMachineKeySequence(event.Sequence) + return d.view.ProcessedMachineKeySequence(event.Sequence, event.CreationDate) } case model.MachineKeyRemoved: err = key.SetData(event) if err != nil { return err } - return d.view.DeleteMachineKey(key.ID, event.Sequence) + return d.view.DeleteMachineKey(key.ID, event.Sequence, event.CreationDate) case model.UserRemoved: - return d.view.DeleteMachineKeysByUserID(event.AggregateID, event.Sequence) + return d.view.DeleteMachineKeysByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return d.view.ProcessedMachineKeySequence(event.Sequence) + return d.view.ProcessedMachineKeySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return d.view.PutMachineKey(key, key.Sequence) + return d.view.PutMachineKey(key, key.Sequence, event.CreationDate) } func (d *MachineKeys) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler") return spooler.HandleError(event, err, d.view.GetLatestMachineKeyFailedEvent, d.view.ProcessedMachineKeyFailedEvent, d.view.ProcessedMachineKeySequence, d.errorCountUntilSkip) } + +func (d *MachineKeys) OnSuccess() error { + return spooler.HandleSuccess(d.view.UpdateMachineKeySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/org.go b/internal/management/repository/eventsourcing/handler/org.go index 1674712059..23155a5c52 100644 --- a/internal/management/repository/eventsourcing/handler/org.go +++ b/internal/management/repository/eventsourcing/handler/org.go @@ -47,15 +47,19 @@ func (o *Org) Reduce(event *es_models.Event) (err error) { } err = org.AppendEvent(event) default: - return o.view.ProcessedOrgSequence(event.Sequence) + return o.view.ProcessedOrgSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return o.view.PutOrg(org) + return o.view.PutOrg(org, event.CreationDate) } func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in project app handler") return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) } + +func (o *Org) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/org_domain.go b/internal/management/repository/eventsourcing/handler/org_domain.go index d3c21aa63e..9dc6307749 100644 --- a/internal/management/repository/eventsourcing/handler/org_domain.go +++ b/internal/management/repository/eventsourcing/handler/org_domain.go @@ -72,7 +72,7 @@ func (d *OrgDomain) processOrgDomain(event *models.Event) (err error) { for _, existingDomain := range existingDomains { existingDomain.Primary = false } - err = d.view.PutOrgDomains(existingDomains, 0) + err = d.view.PutOrgDomains(existingDomains, 0, event.CreationDate) if err != nil { return err } @@ -82,17 +82,21 @@ func (d *OrgDomain) processOrgDomain(event *models.Event) (err error) { if err != nil { return err } - return d.view.DeleteOrgDomain(event.AggregateID, domain.Domain, event.Sequence) + return d.view.DeleteOrgDomain(event.AggregateID, domain.Domain, event.Sequence, event.CreationDate) default: - return d.view.ProcessedOrgDomainSequence(event.Sequence) + return d.view.ProcessedOrgDomainSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return d.view.PutOrgDomain(domain, domain.Sequence) + return d.view.PutOrgDomain(domain, domain.Sequence, event.CreationDate) } func (d *OrgDomain) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-us4sj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgdomain handler") return spooler.HandleError(event, err, d.view.GetLatestOrgDomainFailedEvent, d.view.ProcessedOrgDomainFailedEvent, d.view.ProcessedOrgDomainSequence, d.errorCountUntilSkip) } + +func (o *OrgDomain) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgDomainSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/org_iam_policy.go b/internal/management/repository/eventsourcing/handler/org_iam_policy.go index 7b0b8860ca..fffe08cfec 100644 --- a/internal/management/repository/eventsourcing/handler/org_iam_policy.go +++ b/internal/management/repository/eventsourcing/handler/org_iam_policy.go @@ -53,17 +53,21 @@ func (m *OrgIAMPolicy) processOrgIAMPolicy(event *models.Event) (err error) { } err = policy.AppendEvent(event) case model.OrgIAMPolicyRemoved: - return m.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence) + return m.view.DeleteOrgIAMPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedOrgIAMPolicySequence(event.Sequence) + return m.view.ProcessedOrgIAMPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutOrgIAMPolicy(policy, policy.Sequence) + return m.view.PutOrgIAMPolicy(policy, policy.Sequence, event.CreationDate) } func (m *OrgIAMPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-3Gf9s", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgIAM policy handler") return spooler.HandleError(event, err, m.view.GetLatestOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicyFailedEvent, m.view.ProcessedOrgIAMPolicySequence, m.errorCountUntilSkip) } + +func (o *OrgIAMPolicy) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgIAMPolicySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/org_member.go b/internal/management/repository/eventsourcing/handler/org_member.go index c238c2363a..26f2b2fb86 100644 --- a/internal/management/repository/eventsourcing/handler/org_member.go +++ b/internal/management/repository/eventsourcing/handler/org_member.go @@ -72,14 +72,14 @@ func (m *OrgMember) processOrgMember(event *models.Event) (err error) { if err != nil { return err } - return m.view.DeleteOrgMember(event.AggregateID, member.UserID, event.Sequence) + return m.view.DeleteOrgMember(event.AggregateID, member.UserID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedOrgMemberSequence(event.Sequence) + return m.view.ProcessedOrgMemberSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutOrgMember(member, member.Sequence) + return m.view.PutOrgMember(member, member.Sequence, event.CreationDate) } func (m *OrgMember) processUser(event *models.Event) (err error) { @@ -94,7 +94,7 @@ func (m *OrgMember) processUser(event *models.Event) (err error) { return err } if len(members) == 0 { - return m.view.ProcessedOrgMemberSequence(event.Sequence) + return m.view.ProcessedOrgMemberSequence(event.Sequence, event.CreationDate) } user, err := m.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -103,11 +103,11 @@ func (m *OrgMember) processUser(event *models.Event) (err error) { for _, member := range members { m.fillUserData(member, user) } - return m.view.PutOrgMembers(members, event.Sequence) + return m.view.PutOrgMembers(members, event.Sequence, event.CreationDate) case usr_es_model.UserRemoved: - return m.view.DeleteOrgMembersByUserID(event.AggregateID, event.Sequence) + return m.view.DeleteOrgMembersByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedOrgMemberSequence(event.Sequence) + return m.view.ProcessedOrgMemberSequence(event.Sequence, event.CreationDate) } return nil } @@ -137,3 +137,7 @@ func (m *OrgMember) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-u73es", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler") return spooler.HandleError(event, err, m.view.GetLatestOrgMemberFailedEvent, m.view.ProcessedOrgMemberFailedEvent, m.view.ProcessedOrgMemberSequence, m.errorCountUntilSkip) } + +func (o *OrgMember) OnSuccess() error { + return spooler.HandleSuccess(o.view.UpdateOrgMemberSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/password_age_policy.go b/internal/management/repository/eventsourcing/handler/password_age_policy.go index ccbb1c85f9..535acf03db 100644 --- a/internal/management/repository/eventsourcing/handler/password_age_policy.go +++ b/internal/management/repository/eventsourcing/handler/password_age_policy.go @@ -53,17 +53,21 @@ func (m *PasswordAgePolicy) processPasswordAgePolicy(event *models.Event) (err e } err = policy.AppendEvent(event) case model.PasswordAgePolicyRemoved: - return m.view.DeletePasswordAgePolicy(event.AggregateID, event.Sequence) + return m.view.DeletePasswordAgePolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordAgePolicySequence(event.Sequence) + return m.view.ProcessedPasswordAgePolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordAgePolicy(policy, policy.Sequence) + return m.view.PutPasswordAgePolicy(policy, policy.Sequence, event.CreationDate) } func (m *PasswordAgePolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Bs89f", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordAge policy handler") return spooler.HandleError(event, err, m.view.GetLatestPasswordAgePolicyFailedEvent, m.view.ProcessedPasswordAgePolicyFailedEvent, m.view.ProcessedPasswordAgePolicySequence, m.errorCountUntilSkip) } + +func (m *PasswordAgePolicy) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdatePasswordAgePolicySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/password_complexity_policy.go b/internal/management/repository/eventsourcing/handler/password_complexity_policy.go index 61020657e4..4ada8b0c95 100644 --- a/internal/management/repository/eventsourcing/handler/password_complexity_policy.go +++ b/internal/management/repository/eventsourcing/handler/password_complexity_policy.go @@ -19,12 +19,12 @@ const ( passwordComplexityPolicyTable = "management.password_complexity_policies" ) -func (m *PasswordComplexityPolicy) ViewModel() string { +func (p *PasswordComplexityPolicy) ViewModel() string { return passwordComplexityPolicyTable } -func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordComplexityPolicySequence() +func (p *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordComplexityPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordComplexityPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordComplexityPolicy(event) + err = p.processPasswordComplexityPolicy(event) } return err } -func (m *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { +func (p *PasswordComplexityPolicy) processPasswordComplexityPolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordComplexityPolicyView) switch event.Type { case iam_es_model.PasswordComplexityPolicyAdded, model.PasswordComplexityPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordComplexityPolicyChanged, model.PasswordComplexityPolicyChanged: - policy, err = m.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordComplexityPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordComplexityPolicyRemoved: - return m.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordComplexityPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordComplexityPolicySequence(event.Sequence) + return p.view.ProcessedPasswordComplexityPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordComplexityPolicy(policy, policy.Sequence) + return p.view.PutPasswordComplexityPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { +func (p *PasswordComplexityPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordComplexity policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicyFailedEvent, m.view.ProcessedPasswordComplexityPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicyFailedEvent, p.view.ProcessedPasswordComplexityPolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordComplexityPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdatePasswordComplexityPolicySpoolerRunTimestamp) } diff --git a/internal/management/repository/eventsourcing/handler/password_lockout_policy.go b/internal/management/repository/eventsourcing/handler/password_lockout_policy.go index 0c0f1b93c0..c5a66d9528 100644 --- a/internal/management/repository/eventsourcing/handler/password_lockout_policy.go +++ b/internal/management/repository/eventsourcing/handler/password_lockout_policy.go @@ -19,12 +19,12 @@ const ( passwordLockoutPolicyTable = "management.password_lockout_policies" ) -func (m *PasswordLockoutPolicy) ViewModel() string { +func (p *PasswordLockoutPolicy) ViewModel() string { return passwordLockoutPolicyTable } -func (m *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestPasswordLockoutPolicySequence() +func (p *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { + sequence, err := p.view.GetLatestPasswordLockoutPolicySequence() if err != nil { return nil, err } @@ -33,37 +33,41 @@ func (m *PasswordLockoutPolicy) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *PasswordLockoutPolicy) Reduce(event *models.Event) (err error) { +func (p *PasswordLockoutPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processPasswordLockoutPolicy(event) + err = p.processPasswordLockoutPolicy(event) } return err } -func (m *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *models.Event) (err error) { +func (p *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *models.Event) (err error) { policy := new(iam_model.PasswordLockoutPolicyView) switch event.Type { case iam_es_model.PasswordLockoutPolicyAdded, model.PasswordLockoutPolicyAdded: err = policy.AppendEvent(event) case iam_es_model.PasswordLockoutPolicyChanged, model.PasswordLockoutPolicyChanged: - policy, err = m.view.PasswordLockoutPolicyByAggregateID(event.AggregateID) + policy, err = p.view.PasswordLockoutPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.PasswordLockoutPolicyRemoved: - return m.view.DeletePasswordLockoutPolicy(event.AggregateID, event.Sequence) + return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedPasswordLockoutPolicySequence(event.Sequence) + return p.view.ProcessedPasswordLockoutPolicySequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutPasswordLockoutPolicy(policy, policy.Sequence) + return p.view.PutPasswordLockoutPolicy(policy, policy.Sequence, event.CreationDate) } -func (m *PasswordLockoutPolicy) OnError(event *models.Event, err error) error { +func (p *PasswordLockoutPolicy) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Bms8f", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler") - return spooler.HandleError(event, err, m.view.GetLatestPasswordLockoutPolicyFailedEvent, m.view.ProcessedPasswordLockoutPolicyFailedEvent, m.view.ProcessedPasswordLockoutPolicySequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, p.view.GetLatestPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicySequence, p.errorCountUntilSkip) +} + +func (p *PasswordLockoutPolicy) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdatePasswordLockoutPolicySpoolerRunTimestamp) } diff --git a/internal/management/repository/eventsourcing/handler/project.go b/internal/management/repository/eventsourcing/handler/project.go index 0609d175e3..0536de9ae5 100644 --- a/internal/management/repository/eventsourcing/handler/project.go +++ b/internal/management/repository/eventsourcing/handler/project.go @@ -46,17 +46,21 @@ func (p *Project) Reduce(event *models.Event) (err error) { } err = project.AppendEvent(event) case es_model.ProjectRemoved: - return p.view.DeleteProject(event.AggregateID, event.Sequence) + return p.view.DeleteProject(event.AggregateID, event.Sequence, event.CreationDate) default: - return p.view.ProcessedProjectSequence(event.Sequence) + return p.view.ProcessedProjectSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProject(project) + return p.view.PutProject(project, event.CreationDate) } func (p *Project) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-dLsop3", "id", event.AggregateID).WithError(err).Warn("something went wrong in projecthandler") return spooler.HandleError(event, err, p.view.GetLatestProjectFailedEvent, p.view.ProcessedProjectFailedEvent, p.view.ProcessedProjectSequence, p.errorCountUntilSkip) } + +func (p *Project) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/project_grant.go b/internal/management/repository/eventsourcing/handler/project_grant.go index 3426c40022..1c7306bb21 100644 --- a/internal/management/repository/eventsourcing/handler/project_grant.go +++ b/internal/management/repository/eventsourcing/handler/project_grant.go @@ -2,6 +2,7 @@ package handler import ( "context" + "time" "github.com/caos/logging" @@ -47,7 +48,7 @@ func (p *ProjectGrant) Reduce(event *models.Event) (err error) { if err != nil { return err } - return p.updateExistingProjects(project, event.Sequence) + return p.updateExistingProjects(project, event.Sequence, event.CreationDate) case es_model.ProjectGrantAdded: err = grantedProject.AppendEvent(event) if err != nil { @@ -85,16 +86,16 @@ func (p *ProjectGrant) Reduce(event *models.Event) (err error) { if err != nil { return err } - return p.view.DeleteProjectGrant(grant.GrantID, event.Sequence) + return p.view.DeleteProjectGrant(grant.GrantID, event.Sequence, event.CreationDate) case es_model.ProjectRemoved: return p.view.DeleteProjectGrantsByProjectID(event.AggregateID) default: - return p.view.ProcessedProjectGrantSequence(event.Sequence) + return p.view.ProcessedProjectGrantSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProjectGrant(grantedProject) + return p.view.PutProjectGrant(grantedProject, event.CreationDate) } func (p *ProjectGrant) fillOrgData(grantedProject *view_model.ProjectGrantView, org, resourceOwner *org_model.Org) { @@ -106,7 +107,7 @@ func (p *ProjectGrant) getProject(projectID string) (*proj_model.Project, error) return p.projectEvents.ProjectByID(context.Background(), projectID) } -func (p *ProjectGrant) updateExistingProjects(project *view_model.ProjectView, sequence uint64) error { +func (p *ProjectGrant) updateExistingProjects(project *view_model.ProjectView, sequence uint64, eventTimestamp time.Time) error { projectGrants, err := p.view.ProjectGrantsByProjectID(project.ProjectID) if err != nil { logging.LogWithFields("SPOOL-los03", "id", project.ProjectID).WithError(err).Warn("could not update existing projects") @@ -114,10 +115,14 @@ func (p *ProjectGrant) updateExistingProjects(project *view_model.ProjectView, s for _, existingGrant := range projectGrants { existingGrant.Name = project.Name } - return p.view.PutProjectGrants(projectGrants, sequence) + return p.view.PutProjectGrants(projectGrants, sequence, eventTimestamp) } func (p *ProjectGrant) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-sQqOg", "id", event.AggregateID).WithError(err).Warn("something went wrong in granted projecthandler") return spooler.HandleError(event, err, p.view.GetLatestProjectGrantFailedEvent, p.view.ProcessedProjectGrantFailedEvent, p.view.ProcessedProjectGrantSequence, p.errorCountUntilSkip) } + +func (p *ProjectGrant) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectGrantSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/project_grant_member.go b/internal/management/repository/eventsourcing/handler/project_grant_member.go index a95d83c8c4..fb37af782e 100644 --- a/internal/management/repository/eventsourcing/handler/project_grant_member.go +++ b/internal/management/repository/eventsourcing/handler/project_grant_member.go @@ -72,16 +72,16 @@ func (p *ProjectGrantMember) processProjectGrantMember(event *models.Event) (err if err != nil { return err } - return p.view.DeleteProjectGrantMember(member.GrantID, member.UserID, event.Sequence) + return p.view.DeleteProjectGrantMember(member.GrantID, member.UserID, event.Sequence, event.CreationDate) case proj_es_model.ProjectRemoved: return p.view.DeleteProjectGrantMembersByProjectID(event.AggregateID) default: - return p.view.ProcessedProjectGrantMemberSequence(event.Sequence) + return p.view.ProcessedProjectGrantMemberSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProjectGrantMember(member, member.Sequence) + return p.view.PutProjectGrantMember(member, member.Sequence, event.CreationDate) } func (p *ProjectGrantMember) processUser(event *models.Event) (err error) { @@ -96,7 +96,7 @@ func (p *ProjectGrantMember) processUser(event *models.Event) (err error) { return err } if len(members) == 0 { - return p.view.ProcessedProjectGrantMemberSequence(event.Sequence) + return p.view.ProcessedProjectGrantMemberSequence(event.Sequence, event.CreationDate) } user, err := p.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -105,9 +105,9 @@ func (p *ProjectGrantMember) processUser(event *models.Event) (err error) { for _, member := range members { p.fillUserData(member, user) } - return p.view.PutProjectGrantMembers(members, event.Sequence) + return p.view.PutProjectGrantMembers(members, event.Sequence, event.CreationDate) default: - return p.view.ProcessedProjectGrantMemberSequence(event.Sequence) + return p.view.ProcessedProjectGrantMemberSequence(event.Sequence, event.CreationDate) } return nil } @@ -138,3 +138,7 @@ func (p *ProjectGrantMember) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-kls93", "id", event.AggregateID).WithError(err).Warn("something went wrong in projectmember handler") return spooler.HandleError(event, err, p.view.GetLatestProjectGrantMemberFailedEvent, p.view.ProcessedProjectGrantMemberFailedEvent, p.view.ProcessedProjectGrantMemberSequence, p.errorCountUntilSkip) } + +func (p *ProjectGrantMember) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectGrantMemberSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/project_member.go b/internal/management/repository/eventsourcing/handler/project_member.go index f5df96d10b..7de3603f25 100644 --- a/internal/management/repository/eventsourcing/handler/project_member.go +++ b/internal/management/repository/eventsourcing/handler/project_member.go @@ -72,16 +72,16 @@ func (p *ProjectMember) processProjectMember(event *models.Event) (err error) { if err != nil { return err } - return p.view.DeleteProjectMember(event.AggregateID, member.UserID, event.Sequence) + return p.view.DeleteProjectMember(event.AggregateID, member.UserID, event.Sequence, event.CreationDate) case proj_es_model.ProjectRemoved: return p.view.DeleteProjectMembersByProjectID(event.AggregateID) default: - return p.view.ProcessedProjectMemberSequence(event.Sequence) + return p.view.ProcessedProjectMemberSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProjectMember(member, member.Sequence) + return p.view.PutProjectMember(member, member.Sequence, event.CreationDate) } func (p *ProjectMember) processUser(event *models.Event) (err error) { @@ -96,7 +96,7 @@ func (p *ProjectMember) processUser(event *models.Event) (err error) { return err } if len(members) == 0 { - return p.view.ProcessedProjectMemberSequence(event.Sequence) + return p.view.ProcessedProjectMemberSequence(event.Sequence, event.CreationDate) } user, err := p.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -105,9 +105,9 @@ func (p *ProjectMember) processUser(event *models.Event) (err error) { for _, member := range members { p.fillUserData(member, user) } - return p.view.PutProjectMembers(members, event.Sequence) + return p.view.PutProjectMembers(members, event.Sequence, event.CreationDate) default: - return p.view.ProcessedProjectMemberSequence(event.Sequence) + return p.view.ProcessedProjectMemberSequence(event.Sequence, event.CreationDate) } return nil } @@ -137,3 +137,7 @@ func (p *ProjectMember) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-u73es", "id", event.AggregateID).WithError(err).Warn("something went wrong in projectmember handler") return spooler.HandleError(event, err, p.view.GetLatestProjectMemberFailedEvent, p.view.ProcessedProjectMemberFailedEvent, p.view.ProcessedProjectMemberSequence, p.errorCountUntilSkip) } + +func (p *ProjectMember) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectMemberSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/project_role.go b/internal/management/repository/eventsourcing/handler/project_role.go index 54ec1f81ea..0dba4b14e8 100644 --- a/internal/management/repository/eventsourcing/handler/project_role.go +++ b/internal/management/repository/eventsourcing/handler/project_role.go @@ -52,19 +52,23 @@ func (p *ProjectRole) Reduce(event *models.Event) (err error) { if err != nil { return err } - return p.view.DeleteProjectRole(event.AggregateID, event.ResourceOwner, role.Key, event.Sequence) + return p.view.DeleteProjectRole(event.AggregateID, event.ResourceOwner, role.Key, event.Sequence, event.CreationDate) case es_model.ProjectRemoved: return p.view.DeleteProjectRolesByProjectID(event.AggregateID) default: - return p.view.ProcessedProjectRoleSequence(event.Sequence) + return p.view.ProcessedProjectRoleSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return p.view.PutProjectRole(role) + return p.view.PutProjectRole(role, event.CreationDate) } func (p *ProjectRole) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-lso9w", "id", event.AggregateID).WithError(err).Warn("something went wrong in project role handler") return spooler.HandleError(event, err, p.view.GetLatestProjectRoleFailedEvent, p.view.ProcessedProjectRoleFailedEvent, p.view.ProcessedProjectRoleSequence, p.errorCountUntilSkip) } + +func (p *ProjectRole) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateProjectRoleSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/user.go b/internal/management/repository/eventsourcing/handler/user.go index daf07e3f0c..0721e0fcd3 100644 --- a/internal/management/repository/eventsourcing/handler/user.go +++ b/internal/management/repository/eventsourcing/handler/user.go @@ -91,6 +91,12 @@ func (u *User) ProcessUser(event *models.Event) (err error) { es_model.HumanMFAOTPAdded, es_model.HumanMFAOTPVerified, es_model.HumanMFAOTPRemoved, + es_model.HumanMFAU2FTokenAdded, + es_model.HumanMFAU2FTokenVerified, + es_model.HumanMFAU2FTokenRemoved, + es_model.HumanPasswordlessTokenAdded, + es_model.HumanPasswordlessTokenVerified, + es_model.HumanPasswordlessTokenRemoved, es_model.MachineChanged: user, err = u.view.UserByID(event.AggregateID) if err != nil { @@ -109,14 +115,14 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - return u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutUser(user, user.Sequence) + return u.view.PutUser(user, user.Sequence, event.CreationDate) } func (u *User) ProcessOrg(event *models.Event) (err error) { @@ -130,7 +136,7 @@ func (u *User) ProcessOrg(event *models.Event) (err error) { case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) default: - return u.view.ProcessedUserSequence(event.Sequence) + return u.view.ProcessedUserSequence(event.Sequence, event.CreationDate) } } @@ -153,7 +159,7 @@ func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.SetLoginNames(policy, org.Domains) } - return u.view.PutUsers(users, event.Sequence) + return u.view.PutUsers(users, event.Sequence, event.CreationDate) } func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { @@ -178,7 +184,7 @@ func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { for _, user := range users { user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain) } - return u.view.PutUsers(users, event.Sequence) + return u.view.PutUsers(users, event.Sequence, event.CreationDate) } func (u *User) fillLoginNames(user *view_model.UserView) (err error) { @@ -202,3 +208,7 @@ func (u *User) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-is8wa", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler") return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip) } + +func (u *User) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/user_external_idps.go b/internal/management/repository/eventsourcing/handler/user_external_idps.go index cfcb8f4885..de57a8f0f2 100644 --- a/internal/management/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/management/repository/eventsourcing/handler/user_external_idps.go @@ -30,12 +30,12 @@ const ( externalIDPTable = "management.user_external_idps" ) -func (m *ExternalIDP) ViewModel() string { +func (i *ExternalIDP) ViewModel() string { return externalIDPTable } -func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { - sequence, err := m.view.GetLatestExternalIDPSequence() +func (i *ExternalIDP) EventQuery() (*models.SearchQuery, error) { + sequence, err := i.view.GetLatestExternalIDPSequence() if err != nil { return nil, err } @@ -44,17 +44,17 @@ func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) { LatestSequenceFilter(sequence.CurrentSequence), nil } -func (m *ExternalIDP) Reduce(event *models.Event) (err error) { +func (i *ExternalIDP) Reduce(event *models.Event) (err error) { switch event.AggregateType { case model.UserAggregate: - err = m.processUser(event) + err = i.processUser(event) case iam_es_model.IAMAggregate, org_es_model.OrgAggregate: - err = m.processIdpConfig(event) + err = i.processIdpConfig(event) } return err } -func (m *ExternalIDP) processUser(event *models.Event) (err error) { +func (i *ExternalIDP) processUser(event *models.Event) (err error) { externalIDP := new(usr_view_model.ExternalIDPView) switch event.Type { case model.HumanExternalIDPAdded: @@ -62,25 +62,25 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { if err != nil { return err } - err = m.fillData(externalIDP) + err = i.fillData(externalIDP) case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved: err = externalIDP.SetData(event) if err != nil { return err } - return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + return i.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence, event.CreationDate) case model.UserRemoved: - return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) + return i.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence) + return i.view.PutExternalIDP(externalIDP, externalIDP.Sequence, event.CreationDate) } -func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { +func (i *ExternalIDP) processIdpConfig(event *models.Event) (err error) { switch event.Type { case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: configView := new(iam_view_model.IDPConfigView) @@ -90,45 +90,49 @@ func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { } else { configView.AppendEvent(iam_model.IDPProviderTypeOrg, event) } - exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) + exterinalIDPs, err := i.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID) if err != nil { return err } if event.AggregateType == iam_es_model.IAMAggregate { - config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } else { - config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) } if err != nil { return err } for _, provider := range exterinalIDPs { - m.fillConfigData(provider, config) + i.fillConfigData(provider, config) } - return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...) + return i.view.PutExternalIDPs(event.Sequence, event.CreationDate, exterinalIDPs...) default: - return m.view.ProcessedExternalIDPSequence(event.Sequence) + return i.view.ProcessedExternalIDPSequence(event.Sequence, event.CreationDate) } return nil } -func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { - config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) +func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { + config, err := i.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) if caos_errs.IsNotFound(err) { - config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID) + config, err = i.iamEvents.GetIDPConfig(context.Background(), i.systemDefaults.IamID, externalIDP.IDPConfigID) } if err != nil { return err } - m.fillConfigData(externalIDP, config) + i.fillConfigData(externalIDP, config) return nil } -func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { +func (i *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) { externalIDP.IDPName = config.Name } -func (m *ExternalIDP) OnError(event *models.Event, err error) error { +func (i *ExternalIDP) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler") - return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip) + return spooler.HandleError(event, err, i.view.GetLatestExternalIDPFailedEvent, i.view.ProcessedExternalIDPFailedEvent, i.view.ProcessedExternalIDPSequence, i.errorCountUntilSkip) +} + +func (i *ExternalIDP) OnSuccess() error { + return spooler.HandleSuccess(i.view.UpdateExternalIDPSpoolerRunTimestamp) } diff --git a/internal/management/repository/eventsourcing/handler/user_grant.go b/internal/management/repository/eventsourcing/handler/user_grant.go index e95b153299..0ffe6955ac 100644 --- a/internal/management/repository/eventsourcing/handler/user_grant.go +++ b/internal/management/repository/eventsourcing/handler/user_grant.go @@ -79,14 +79,14 @@ func (u *UserGrant) processUserGrant(event *models.Event) (err error) { } err = grant.AppendEvent(event) case grant_es_model.UserGrantRemoved, grant_es_model.UserGrantCascadeRemoved: - return u.view.DeleteUserGrant(event.AggregateID, event.Sequence) + return u.view.DeleteUserGrant(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutUserGrant(grant, grant.Sequence) + return u.view.PutUserGrant(grant, grant.Sequence, event.CreationDate) } func (u *UserGrant) processUser(event *models.Event) (err error) { @@ -101,7 +101,7 @@ func (u *UserGrant) processUser(event *models.Event) (err error) { return err } if len(grants) == 0 { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } user, err := u.userEvents.UserByID(context.Background(), event.AggregateID) if err != nil { @@ -110,9 +110,9 @@ func (u *UserGrant) processUser(event *models.Event) (err error) { for _, grant := range grants { u.fillUserData(grant, user) } - return u.view.PutUserGrants(grants, event.Sequence) + return u.view.PutUserGrants(grants, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } return nil } @@ -125,7 +125,7 @@ func (u *UserGrant) processProject(event *models.Event) (err error) { return err } if len(grants) == 0 { - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } project, err := u.projectEvents.ProjectByID(context.Background(), event.AggregateID) if err != nil { @@ -134,9 +134,9 @@ func (u *UserGrant) processProject(event *models.Event) (err error) { for _, grant := range grants { u.fillProjectData(grant, project) } - return u.view.PutUserGrants(grants, event.Sequence) + return u.view.PutUserGrants(grants, event.Sequence, event.CreationDate) default: - return u.view.ProcessedUserGrantSequence(event.Sequence) + return u.view.ProcessedUserGrantSequence(event.Sequence, event.CreationDate) } return nil } @@ -193,3 +193,7 @@ func (u *UserGrant) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-8is4s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler") return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip) } + +func (u *UserGrant) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateUserGrantSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/user_membership.go b/internal/management/repository/eventsourcing/handler/user_membership.go index af19ad9b81..8537905d47 100644 --- a/internal/management/repository/eventsourcing/handler/user_membership.go +++ b/internal/management/repository/eventsourcing/handler/user_membership.go @@ -73,14 +73,14 @@ func (m *UserMembership) processIam(event *models.Event) (err error) { } err = member.AppendEvent(event) case iam_es_model.IAMMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillIamDisplayName(member *usr_es_model.UserMembershipView) { @@ -103,16 +103,16 @@ func (m *UserMembership) processOrg(event *models.Event) (err error) { } err = member.AppendEvent(event) case org_es_model.OrgMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence, event.CreationDate) case org_es_model.OrgChanged: return m.updateOrgDisplayName(event) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillOrgDisplayName(member *usr_es_model.UserMembershipView) (err error) { @@ -137,7 +137,7 @@ func (m *UserMembership) updateOrgDisplayName(event *models.Event) error { for _, membership := range memberships { membership.DisplayName = org.Name } - return m.view.BulkPutUserMemberships(memberships, event.Sequence) + return m.view.BulkPutUserMemberships(memberships, event.Sequence, event.CreationDate) } func (m *UserMembership) processProject(event *models.Event) (err error) { @@ -156,7 +156,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) { } err = member.AppendEvent(event) case proj_es_model.ProjectMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence, event.CreationDate) case proj_es_model.ProjectGrantMemberChanged: member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant) if err != nil { @@ -164,20 +164,20 @@ func (m *UserMembership) processProject(event *models.Event) (err error) { } err = member.AppendEvent(event) case proj_es_model.ProjectGrantMemberRemoved: - return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence) + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence, event.CreationDate) case proj_es_model.ProjectChanged: return m.updateProjectDisplayName(event) case proj_es_model.ProjectRemoved: - return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence) + return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence, event.CreationDate) case proj_es_model.ProjectGrantRemoved: - return m.view.DeleteUserMembershipsByAggregateIDAndObjectID(event.AggregateID, member.ObjectID, event.Sequence) + return m.view.DeleteUserMembershipsByAggregateIDAndObjectID(event.AggregateID, member.ObjectID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return m.view.PutUserMembership(member, event.Sequence) + return m.view.PutUserMembership(member, event.Sequence, event.CreationDate) } func (m *UserMembership) fillProjectDisplayName(member *usr_es_model.UserMembershipView) (err error) { @@ -202,15 +202,15 @@ func (m *UserMembership) updateProjectDisplayName(event *models.Event) error { for _, membership := range memberships { membership.DisplayName = project.Name } - return m.view.BulkPutUserMemberships(memberships, event.Sequence) + return m.view.BulkPutUserMemberships(memberships, event.Sequence, event.CreationDate) } func (m *UserMembership) processUser(event *models.Event) (err error) { switch event.Type { case model.UserRemoved: - return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence) + return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence, event.CreationDate) default: - return m.view.ProcessedUserMembershipSequence(event.Sequence) + return m.view.ProcessedUserMembershipSequence(event.Sequence, event.CreationDate) } } @@ -218,3 +218,7 @@ func (m *UserMembership) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Fwer2", "id", event.AggregateID).WithError(err).Warn("something went wrong in user membership handler") return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip) } + +func (m *UserMembership) OnSuccess() error { + return spooler.HandleSuccess(m.view.UpdateUserMembershipSpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/view/application.go b/internal/management/repository/eventsourcing/view/application.go index 3d25e2d1d9..46672fc74a 100644 --- a/internal/management/repository/eventsourcing/view/application.go +++ b/internal/management/repository/eventsourcing/view/application.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -23,28 +24,28 @@ func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) return view.SearchApplications(v.Db, applicationTable, request) } -func (v *View) PutApplication(app *model.ApplicationView) error { +func (v *View) PutApplication(app *model.ApplicationView, eventTimestamp time.Time) error { err := view.PutApplication(v.Db, applicationTable, app) if err != nil { return err } - return v.ProcessedApplicationSequence(app.Sequence) + return v.ProcessedApplicationSequence(app.Sequence, eventTimestamp) } -func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64) error { +func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64, eventTimestamp time.Time) error { err := view.PutApplications(v.Db, applicationTable, apps...) if err != nil { return err } - return v.ProcessedApplicationSequence(sequence) + return v.ProcessedApplicationSequence(sequence, eventTimestamp) } -func (v *View) DeleteApplication(appID string, eventSequence uint64) error { +func (v *View) DeleteApplication(appID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteApplication(v.Db, applicationTable, appID) if err != nil { return nil } - return v.ProcessedApplicationSequence(eventSequence) + return v.ProcessedApplicationSequence(eventSequence, eventTimestamp) } func (v *View) DeleteApplicationsByProjectID(projectID string) error { @@ -55,8 +56,12 @@ func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, erro return v.latestSequence(applicationTable) } -func (v *View) ProcessedApplicationSequence(eventSequence uint64) error { - return v.saveCurrentSequence(applicationTable, eventSequence) +func (v *View) ProcessedApplicationSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(applicationTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateApplicationSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(applicationTable) } func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/external_idps.go b/internal/management/repository/eventsourcing/view/external_idps.go index e7f9f9fd0b..c2944c7a5f 100644 --- a/internal/management/repository/eventsourcing/view/external_idps.go +++ b/internal/management/repository/eventsourcing/view/external_idps.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -32,45 +33,48 @@ func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ( return view.SearchExternalIDPs(v.Db, externalIDPTable, request) } -func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error { +func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64, eventTimestamp time.Time) error { err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error { +func (v *View) PutExternalIDPs(sequence uint64, eventTimestamp time.Time, externalIDPs ...*model.ExternalIDPView) error { err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...) if err != nil { return err } - return v.ProcessedExternalIDPSequence(sequence) + return v.ProcessedExternalIDPSequence(sequence, eventTimestamp) } -func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) if err != nil { return err } - return v.ProcessedExternalIDPSequence(eventSequence) + return v.ProcessedExternalIDPSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } -func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error { - return v.saveCurrentSequence(externalIDPTable, eventSequence) +func (v *View) ProcessedExternalIDPSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(externalIDPTable, eventSequence, eventTimestamp) } +func (v *View) UpdateExternalIDPSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(externalIDPTable) +} func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { return v.latestFailedEvent(externalIDPTable, sequence) } diff --git a/internal/management/repository/eventsourcing/view/idp_configs.go b/internal/management/repository/eventsourcing/view/idp_configs.go index 6a53e228de..a9dfde45da 100644 --- a/internal/management/repository/eventsourcing/view/idp_configs.go +++ b/internal/management/repository/eventsourcing/view/idp_configs.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -20,28 +21,32 @@ func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*i return view.SearchIDPs(v.Db, idpConfigTable, request) } -func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, sequence uint64) error { +func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDP(v.Db, idpConfigTable, idp) if err != nil { return err } - return v.ProcessedIDPConfigSequence(sequence) + return v.ProcessedIDPConfigSequence(sequence, eventTimestamp) } -func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64) error { +func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDP(v.Db, idpConfigTable, idpID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIDPConfigSequence(eventSequence) + return v.ProcessedIDPConfigSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestIDPConfigSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpConfigTable) } -func (v *View) ProcessedIDPConfigSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpConfigTable, eventSequence) +func (v *View) ProcessedIDPConfigSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpConfigTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateIDPConfigSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpConfigTable) } func (v *View) GetLatestIDPConfigFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/idp_providers.go b/internal/management/repository/eventsourcing/view/idp_providers.go index def9aa78ca..207feb621f 100644 --- a/internal/management/repository/eventsourcing/view/idp_providers.go +++ b/internal/management/repository/eventsourcing/view/idp_providers.go @@ -6,68 +6,73 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( idpProviderTable = "management.idp_providers" ) -func (v *View) IdpProviderByAggregateAndIdpConfigID(aggregateID, idpConfigID string) (*model.IDPProviderView, error) { +func (v *View) IDPProviderByAggregateAndIdpConfigID(aggregateID, idpConfigID string) (*model.IDPProviderView, error) { return view.GetIDPProviderByAggregateIDAndConfigID(v.Db, idpProviderTable, aggregateID, idpConfigID) } -func (v *View) IdpProvidersByIdpConfigID(aggregateID, idpConfigID string) ([]*model.IDPProviderView, error) { +func (v *View) IDPProvidersByIdpConfigID(aggregateID, idpConfigID string) ([]*model.IDPProviderView, error) { return view.IDPProvidersByIdpConfigID(v.Db, idpProviderTable, idpConfigID) } -func (v *View) SearchIdpProviders(request *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) { +func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) { return view.SearchIDPProviders(v.Db, idpProviderTable, request) } -func (v *View) PutIdpProvider(provider *model.IDPProviderView, sequence uint64) error { +func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64, eventTimestamp time.Time) error { err := view.PutIDPProvider(v.Db, idpProviderTable, provider) if err != nil { return err } - return v.ProcessedIdpProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) PutIdpProviders(sequence uint64, providers ...*model.IDPProviderView) error { +func (v *View) PutIDPProviders(sequence uint64, eventTimestamp time.Time, providers ...*model.IDPProviderView) error { err := view.PutIDPProviders(v.Db, idpProviderTable, providers...) if err != nil { return err } - return v.ProcessedIdpProviderSequence(sequence) + return v.ProcessedIDPProviderSequence(sequence, eventTimestamp) } -func (v *View) DeleteIdpProvider(aggregateID, idpConfigID string, eventSequence uint64) error { +func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIdpProviderSequence(eventSequence) + return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteIdpProvidersByAggregateID(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteIDPProvidersByAggregateID(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteIDPProvidersByAggregateID(v.Db, idpProviderTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedIdpProviderSequence(eventSequence) + return v.ProcessedIDPProviderSequence(eventSequence, eventTimestamp) } -func (v *View) GetLatestIdpProviderSequence() (*global_view.CurrentSequence, error) { +func (v *View) GetLatestIDPProviderSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(idpProviderTable) } -func (v *View) ProcessedIdpProviderSequence(eventSequence uint64) error { - return v.saveCurrentSequence(idpProviderTable, eventSequence) +func (v *View) ProcessedIDPProviderSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(idpProviderTable, eventSequence, eventTimestamp) } -func (v *View) GetLatestIdpProviderFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { +func (v *View) UpdateIDPProviderSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(idpProviderTable) +} + +func (v *View) GetLatestIDPProviderFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { return v.latestFailedEvent(idpProviderTable, sequence) } -func (v *View) ProcessedIdpProviderFailedEvent(failedEvent *global_view.FailedEvent) error { +func (v *View) ProcessedIDPProviderFailedEvent(failedEvent *global_view.FailedEvent) error { return v.saveFailedEvent(failedEvent) } diff --git a/internal/management/repository/eventsourcing/view/label_policies.go b/internal/management/repository/eventsourcing/view/label_policies.go index 182ca4f368..27b234fc29 100644 --- a/internal/management/repository/eventsourcing/view/label_policies.go +++ b/internal/management/repository/eventsourcing/view/label_policies.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyV return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID) } -func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, sequence uint64) error { +func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutLabelPolicy(v.Db, labelPolicyTable, policy) if err != nil { return err } - return v.ProcessedLabelPolicySequence(sequence) + return v.ProcessedLabelPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteLabelPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteLabelPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteLabelPolicy(v.Db, labelPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedLabelPolicySequence(eventSequence) + return v.ProcessedLabelPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestLabelPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(labelPolicyTable) } -func (v *View) ProcessedLabelPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(labelPolicyTable, eventSequence) +func (v *View) ProcessedLabelPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(labelPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateLabelPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(labelPolicyTable) } func (v *View) GetLatestLabelPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/login_policies.go b/internal/management/repository/eventsourcing/view/login_policies.go index 741dbc9322..c37f4283d6 100644 --- a/internal/management/repository/eventsourcing/view/login_policies.go +++ b/internal/management/repository/eventsourcing/view/login_policies.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyV return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64) error { +func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) if err != nil { return err } - return v.ProcessedLoginPolicySequence(sequence) + return v.ProcessedLoginPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedLoginPolicySequence(eventSequence) + return v.ProcessedLoginPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(loginPolicyTable) } -func (v *View) ProcessedLoginPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(loginPolicyTable, eventSequence) +func (v *View) ProcessedLoginPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(loginPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(loginPolicyTable) } func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/machine_keys.go b/internal/management/repository/eventsourcing/view/machine_keys.go index 013183cc64..b037137c82 100644 --- a/internal/management/repository/eventsourcing/view/machine_keys.go +++ b/internal/management/repository/eventsourcing/view/machine_keys.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -23,39 +24,43 @@ func (v *View) SearchMachineKeys(request *usr_model.MachineKeySearchRequest) ([] return view.SearchMachineKeys(v.Db, machineKeyTable, request) } -func (v *View) PutMachineKey(org *model.MachineKeyView, sequence uint64) error { +func (v *View) PutMachineKey(org *model.MachineKeyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutMachineKey(v.Db, machineKeyTable, org) if err != nil { return err } if sequence != 0 { - return v.ProcessedMachineKeySequence(sequence) + return v.ProcessedMachineKeySequence(sequence, eventTimestamp) } return nil } -func (v *View) DeleteMachineKey(keyID string, eventSequence uint64) error { +func (v *View) DeleteMachineKey(keyID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteMachineKey(v.Db, machineKeyTable, keyID) if err != nil { return nil } - return v.ProcessedMachineKeySequence(eventSequence) + return v.ProcessedMachineKeySequence(eventSequence, eventTimestamp) } -func (v *View) DeleteMachineKeysByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteMachineKeysByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteMachineKey(v.Db, machineKeyTable, userID) if err != nil { return nil } - return v.ProcessedMachineKeySequence(eventSequence) + return v.ProcessedMachineKeySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestMachineKeySequence() (*repository.CurrentSequence, error) { return v.latestSequence(machineKeyTable) } -func (v *View) ProcessedMachineKeySequence(eventSequence uint64) error { - return v.saveCurrentSequence(machineKeyTable, eventSequence) +func (v *View) ProcessedMachineKeySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(machineKeyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateMachineKeySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(machineKeyTable) } func (v *View) GetLatestMachineKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/org.go b/internal/management/repository/eventsourcing/view/org.go index 80b35435b2..a5de5fdb46 100644 --- a/internal/management/repository/eventsourcing/view/org.go +++ b/internal/management/repository/eventsourcing/view/org.go @@ -4,6 +4,7 @@ import ( org_view "github.com/caos/zitadel/internal/org/repository/view" "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -14,12 +15,12 @@ func (v *View) OrgByID(orgID string) (*model.OrgView, error) { return org_view.OrgByID(v.Db, orgTable, orgID) } -func (v *View) PutOrg(org *model.OrgView) error { +func (v *View) PutOrg(org *model.OrgView, eventTimestamp time.Time) error { err := org_view.PutOrg(v.Db, orgTable, org) if err != nil { return err } - return v.ProcessedOrgSequence(org.Sequence) + return v.ProcessedOrgSequence(org.Sequence, eventTimestamp) } func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { @@ -30,10 +31,14 @@ func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) erro return v.saveFailedEvent(failedEvent) } +func (v *View) UpdateOrgSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgTable) +} + func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgTable) } -func (v *View) ProcessedOrgSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgTable, eventSequence) +func (v *View) ProcessedOrgSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgTable, eventSequence, eventTimestamp) } diff --git a/internal/management/repository/eventsourcing/view/org_domain.go b/internal/management/repository/eventsourcing/view/org_domain.go index 474a481d43..94f3f84c95 100644 --- a/internal/management/repository/eventsourcing/view/org_domain.go +++ b/internal/management/repository/eventsourcing/view/org_domain.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/org/repository/view" "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -27,39 +28,43 @@ func (v *View) SearchOrgDomains(request *org_model.OrgDomainSearchRequest) ([]*m return view.SearchOrgDomains(v.Db, orgDomainTable, request) } -func (v *View) PutOrgDomain(org *model.OrgDomainView, sequence uint64) error { +func (v *View) PutOrgDomain(org *model.OrgDomainView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgDomain(v.Db, orgDomainTable, org) if err != nil { return err } if sequence != 0 { - return v.ProcessedOrgDomainSequence(sequence) + return v.ProcessedOrgDomainSequence(sequence, eventTimestamp) } return nil } -func (v *View) PutOrgDomains(domains []*model.OrgDomainView, sequence uint64) error { +func (v *View) PutOrgDomains(domains []*model.OrgDomainView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgDomains(v.Db, orgDomainTable, domains...) if err != nil { return err } - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } -func (v *View) DeleteOrgDomain(orgID, domain string, eventSequence uint64) error { +func (v *View) DeleteOrgDomain(orgID, domain string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgDomain(v.Db, orgDomainTable, orgID, domain) if err != nil { return nil } - return v.ProcessedOrgDomainSequence(eventSequence) + return v.ProcessedOrgDomainSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestOrgDomainSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgDomainTable) } -func (v *View) ProcessedOrgDomainSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgDomainTable, eventSequence) +func (v *View) ProcessedOrgDomainSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgDomainTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgDomainSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgDomainTable) } func (v *View) GetLatestOrgDomainFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/org_iam_policy.go b/internal/management/repository/eventsourcing/view/org_iam_policy.go index 81cb67c4ac..db9d113155 100644 --- a/internal/management/repository/eventsourcing/view/org_iam_policy.go +++ b/internal/management/repository/eventsourcing/view/org_iam_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) OrgIAMPolicyByAggregateID(aggregateID string) (*model.OrgIAMPolic return view.GetOrgIAMPolicyByAggregateID(v.Db, orgIAMPolicyTable, aggregateID) } -func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64) error { +func (v *View) PutOrgIAMPolicy(policy *model.OrgIAMPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgIAMPolicy(v.Db, orgIAMPolicyTable, policy) if err != nil { return err } - return v.ProcessedOrgIAMPolicySequence(sequence) + return v.ProcessedOrgIAMPolicySequence(sequence, eventTimestamp) } -func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteOrgIAMPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgIAMPolicy(v.Db, orgIAMPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedOrgIAMPolicySequence(eventSequence) + return v.ProcessedOrgIAMPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestOrgIAMPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(orgIAMPolicyTable) } -func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence) +func (v *View) ProcessedOrgIAMPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgIAMPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgIAMPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgIAMPolicyTable) } func (v *View) GetLatestOrgIAMPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/org_member.go b/internal/management/repository/eventsourcing/view/org_member.go index c4396d5a50..d1cb20b3c0 100644 --- a/internal/management/repository/eventsourcing/view/org_member.go +++ b/internal/management/repository/eventsourcing/view/org_member.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/org/repository/view" "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -23,44 +24,48 @@ func (v *View) OrgMembersByUserID(userID string) ([]*model.OrgMemberView, error) return view.OrgMembersByUserID(v.Db, orgMemberTable, userID) } -func (v *View) PutOrgMember(member *model.OrgMemberView, sequence uint64) error { +func (v *View) PutOrgMember(member *model.OrgMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgMember(v.Db, orgMemberTable, member) if err != nil { return err } - return v.ProcessedOrgMemberSequence(sequence) + return v.ProcessedOrgMemberSequence(sequence, eventTimestamp) } -func (v *View) PutOrgMembers(members []*model.OrgMemberView, sequence uint64) error { +func (v *View) PutOrgMembers(members []*model.OrgMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutOrgMembers(v.Db, orgMemberTable, members...) if err != nil { return err } - return v.ProcessedOrgMemberSequence(sequence) + return v.ProcessedOrgMemberSequence(sequence, eventTimestamp) } -func (v *View) DeleteOrgMember(orgID, userID string, eventSequence uint64) error { +func (v *View) DeleteOrgMember(orgID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgMember(v.Db, orgMemberTable, orgID, userID) if err != nil { return nil } - return v.ProcessedOrgMemberSequence(eventSequence) + return v.ProcessedOrgMemberSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteOrgMembersByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteOrgMembersByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteOrgMembersByUserID(v.Db, orgMemberTable, userID) if err != nil { return nil } - return v.ProcessedOrgMemberSequence(eventSequence) + return v.ProcessedOrgMemberSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestOrgMemberSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgMemberTable) } -func (v *View) ProcessedOrgMemberSequence(eventSequence uint64) error { - return v.saveCurrentSequence(orgMemberTable, eventSequence) +func (v *View) ProcessedOrgMemberSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(orgMemberTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateOrgMemberSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgMemberTable) } func (v *View) GetLatestOrgMemberFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/password_age_policy.go b/internal/management/repository/eventsourcing/view/password_age_policy.go index 3d6caaa8a1..e37d650072 100644 --- a/internal/management/repository/eventsourcing/view/password_age_policy.go +++ b/internal/management/repository/eventsourcing/view/password_age_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordAgePolicyByAggregateID(aggregateID string) (*model.Passwo return view.GetPasswordAgePolicyByAggregateID(v.Db, passwordAgePolicyTable, aggregateID) } -func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, sequence uint64) error { +func (v *View) PutPasswordAgePolicy(policy *model.PasswordAgePolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordAgePolicy(v.Db, passwordAgePolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordAgePolicySequence(sequence) + return v.ProcessedPasswordAgePolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordAgePolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordAgePolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordAgePolicy(v.Db, passwordAgePolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordAgePolicySequence(eventSequence) + return v.ProcessedPasswordAgePolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordAgePolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordAgePolicyTable) } -func (v *View) ProcessedPasswordAgePolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordAgePolicyTable, eventSequence) +func (v *View) ProcessedPasswordAgePolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordAgePolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordAgePolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordAgePolicyTable) } func (v *View) GetLatestPasswordAgePolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/password_complexity_policy.go b/internal/management/repository/eventsourcing/view/password_complexity_policy.go index 040eb1d596..70895d13a9 100644 --- a/internal/management/repository/eventsourcing/view/password_complexity_policy.go +++ b/internal/management/repository/eventsourcing/view/password_complexity_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordComplexityPolicyByAggregateID(aggregateID string) (*model return view.GetPasswordComplexityPolicyByAggregateID(v.Db, passwordComplexityPolicyTable, aggregateID) } -func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64) error { +func (v *View) PutPasswordComplexityPolicy(policy *model.PasswordComplexityPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordComplexityPolicySequence(sequence) + return v.ProcessedPasswordComplexityPolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordComplexityPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordComplexityPolicy(v.Db, passwordComplexityPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordComplexityPolicySequence(eventSequence) + return v.ProcessedPasswordComplexityPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordComplexityPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordComplexityPolicyTable) } -func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence) +func (v *View) ProcessedPasswordComplexityPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordComplexityPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordComplexityPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordComplexityPolicyTable) } func (v *View) GetLatestPasswordComplexityPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/password_lockout_policy.go b/internal/management/repository/eventsourcing/view/password_lockout_policy.go index 099b961f57..11e3955720 100644 --- a/internal/management/repository/eventsourcing/view/password_lockout_policy.go +++ b/internal/management/repository/eventsourcing/view/password_lockout_policy.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/iam/repository/view" "github.com/caos/zitadel/internal/iam/repository/view/model" global_view "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -15,28 +16,32 @@ func (v *View) PasswordLockoutPolicyByAggregateID(aggregateID string) (*model.Pa return view.GetPasswordLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID) } -func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, sequence uint64) error { +func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, sequence uint64, eventTimestamp time.Time) error { err := view.PutPasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy) if err != nil { return err } - return v.ProcessedPasswordLockoutPolicySequence(sequence) + return v.ProcessedPasswordLockoutPolicySequence(sequence, eventTimestamp) } -func (v *View) DeletePasswordLockoutPolicy(aggregateID string, eventSequence uint64) error { +func (v *View) DeletePasswordLockoutPolicy(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeletePasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { return err } - return v.ProcessedPasswordLockoutPolicySequence(eventSequence) + return v.ProcessedPasswordLockoutPolicySequence(eventSequence, eventTimestamp) } func (v *View) GetLatestPasswordLockoutPolicySequence() (*global_view.CurrentSequence, error) { return v.latestSequence(passwordLockoutPolicyTable) } -func (v *View) ProcessedPasswordLockoutPolicySequence(eventSequence uint64) error { - return v.saveCurrentSequence(passwordLockoutPolicyTable, eventSequence) +func (v *View) ProcessedPasswordLockoutPolicySequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(passwordLockoutPolicyTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdatePasswordLockoutPolicySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(passwordLockoutPolicyTable) } func (v *View) GetLatestPasswordLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/project.go b/internal/management/repository/eventsourcing/view/project.go index f0f5c7d882..fede8830ee 100644 --- a/internal/management/repository/eventsourcing/view/project.go +++ b/internal/management/repository/eventsourcing/view/project.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -19,28 +20,32 @@ func (v *View) SearchProjects(request *proj_model.ProjectViewSearchRequest) ([]* return view.SearchProjects(v.Db, projectTable, request) } -func (v *View) PutProject(project *model.ProjectView) error { +func (v *View) PutProject(project *model.ProjectView, eventTimestamp time.Time) error { err := view.PutProject(v.Db, projectTable, project) if err != nil { return err } - return v.ProcessedProjectSequence(project.Sequence) + return v.ProcessedProjectSequence(project.Sequence, eventTimestamp) } -func (v *View) DeleteProject(projectID string, eventSequence uint64) error { +func (v *View) DeleteProject(projectID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProject(v.Db, projectTable, projectID) if err != nil { return nil } - return v.ProcessedProjectSequence(eventSequence) + return v.ProcessedProjectSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestProjectSequence() (*repository.CurrentSequence, error) { return v.latestSequence(projectTable) } -func (v *View) ProcessedProjectSequence(eventSequence uint64) error { - return v.saveCurrentSequence(projectTable, eventSequence) +func (v *View) ProcessedProjectSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(projectTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(projectTable) } func (v *View) GetLatestProjectFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/project_grant.go b/internal/management/repository/eventsourcing/view/project_grant.go index f0cdd3330a..64f1f6272e 100644 --- a/internal/management/repository/eventsourcing/view/project_grant.go +++ b/internal/management/repository/eventsourcing/view/project_grant.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -31,28 +32,28 @@ func (v *View) SearchProjectGrants(request *proj_model.ProjectGrantViewSearchReq return view.SearchProjectGrants(v.Db, grantedProjectTable, request) } -func (v *View) PutProjectGrant(grant *model.ProjectGrantView) error { +func (v *View) PutProjectGrant(grant *model.ProjectGrantView, eventTimestamp time.Time) error { err := view.PutProjectGrant(v.Db, grantedProjectTable, grant) if err != nil { return err } - return v.ProcessedProjectGrantSequence(grant.Sequence) + return v.ProcessedProjectGrantSequence(grant.Sequence, eventTimestamp) } -func (v *View) PutProjectGrants(grants []*model.ProjectGrantView, sequence uint64) error { +func (v *View) PutProjectGrants(grants []*model.ProjectGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutProjectGrants(v.Db, grantedProjectTable, grants...) if err != nil { return err } - return v.ProcessedProjectGrantSequence(sequence) + return v.ProcessedProjectGrantSequence(sequence, eventTimestamp) } -func (v *View) DeleteProjectGrant(grantID string, eventSequence uint64) error { +func (v *View) DeleteProjectGrant(grantID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProjectGrant(v.Db, grantedProjectTable, grantID) if err != nil { return err } - return v.ProcessedProjectGrantSequence(eventSequence) + return v.ProcessedProjectGrantSequence(eventSequence, eventTimestamp) } func (v *View) DeleteProjectGrantsByProjectID(projectID string) error { @@ -63,8 +64,12 @@ func (v *View) GetLatestProjectGrantSequence() (*repository.CurrentSequence, err return v.latestSequence(grantedProjectTable) } -func (v *View) ProcessedProjectGrantSequence(eventSequence uint64) error { - return v.saveCurrentSequence(grantedProjectTable, eventSequence) +func (v *View) ProcessedProjectGrantSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(grantedProjectTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectGrantSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(grantedProjectTable) } func (v *View) GetLatestProjectGrantFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/project_grant_member.go b/internal/management/repository/eventsourcing/view/project_grant_member.go index 39e9521a77..b9cf4391ba 100644 --- a/internal/management/repository/eventsourcing/view/project_grant_member.go +++ b/internal/management/repository/eventsourcing/view/project_grant_member.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -27,28 +28,28 @@ func (v *View) ProjectGrantMembersByUserID(userID string) ([]*model.ProjectGrant return view.ProjectGrantMembersByUserID(v.Db, projectGrantMemberTable, userID) } -func (v *View) PutProjectGrantMember(member *model.ProjectGrantMemberView, sequence uint64) error { +func (v *View) PutProjectGrantMember(member *model.ProjectGrantMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutProjectGrantMember(v.Db, projectGrantMemberTable, member) if err != nil { return err } - return v.ProcessedProjectGrantMemberSequence(sequence) + return v.ProcessedProjectGrantMemberSequence(sequence, eventTimestamp) } -func (v *View) PutProjectGrantMembers(members []*model.ProjectGrantMemberView, sequence uint64) error { +func (v *View) PutProjectGrantMembers(members []*model.ProjectGrantMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutProjectGrantMembers(v.Db, projectGrantMemberTable, members...) if err != nil { return err } - return v.ProcessedProjectGrantMemberSequence(sequence) + return v.ProcessedProjectGrantMemberSequence(sequence, eventTimestamp) } -func (v *View) DeleteProjectGrantMember(grantID, userID string, eventSequence uint64) error { +func (v *View) DeleteProjectGrantMember(grantID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProjectGrantMember(v.Db, projectGrantMemberTable, grantID, userID) if err != nil { return nil } - return v.ProcessedProjectGrantMemberSequence(eventSequence) + return v.ProcessedProjectGrantMemberSequence(eventSequence, eventTimestamp) } func (v *View) DeleteProjectGrantMembersByProjectID(projectID string) error { @@ -59,8 +60,12 @@ func (v *View) GetLatestProjectGrantMemberSequence() (*repository.CurrentSequenc return v.latestSequence(projectGrantMemberTable) } -func (v *View) ProcessedProjectGrantMemberSequence(eventSequence uint64) error { - return v.saveCurrentSequence(projectGrantMemberTable, eventSequence) +func (v *View) ProcessedProjectGrantMemberSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(projectGrantMemberTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectGrantMemberSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(projectGrantMemberTable) } func (v *View) GetLatestProjectGrantMemberFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/project_member.go b/internal/management/repository/eventsourcing/view/project_member.go index 717d827d7b..c21dee73e2 100644 --- a/internal/management/repository/eventsourcing/view/project_member.go +++ b/internal/management/repository/eventsourcing/view/project_member.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -27,28 +28,28 @@ func (v *View) ProjectMembersByUserID(userID string) ([]*model.ProjectMemberView return view.ProjectMembersByUserID(v.Db, projectMemberTable, userID) } -func (v *View) PutProjectMember(project *model.ProjectMemberView, sequence uint64) error { +func (v *View) PutProjectMember(project *model.ProjectMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutProjectMember(v.Db, projectMemberTable, project) if err != nil { return err } - return v.ProcessedProjectMemberSequence(sequence) + return v.ProcessedProjectMemberSequence(sequence, eventTimestamp) } -func (v *View) PutProjectMembers(project []*model.ProjectMemberView, sequence uint64) error { +func (v *View) PutProjectMembers(project []*model.ProjectMemberView, sequence uint64, eventTimestamp time.Time) error { err := view.PutProjectMembers(v.Db, projectMemberTable, project...) if err != nil { return err } - return v.ProcessedProjectMemberSequence(sequence) + return v.ProcessedProjectMemberSequence(sequence, eventTimestamp) } -func (v *View) DeleteProjectMember(projectID, userID string, eventSequence uint64) error { +func (v *View) DeleteProjectMember(projectID, userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProjectMember(v.Db, projectMemberTable, projectID, userID) if err != nil { return nil } - return v.ProcessedProjectMemberSequence(eventSequence) + return v.ProcessedProjectMemberSequence(eventSequence, eventTimestamp) } func (v *View) DeleteProjectMembersByProjectID(projectID string) error { @@ -59,8 +60,12 @@ func (v *View) GetLatestProjectMemberSequence() (*repository.CurrentSequence, er return v.latestSequence(projectMemberTable) } -func (v *View) ProcessedProjectMemberSequence(eventSequence uint64) error { - return v.saveCurrentSequence(projectMemberTable, eventSequence) +func (v *View) ProcessedProjectMemberSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(projectMemberTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectMemberSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(projectMemberTable) } func (v *View) GetLatestProjectMemberFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/project_role.go b/internal/management/repository/eventsourcing/view/project_role.go index b8276471ed..6703a93efd 100644 --- a/internal/management/repository/eventsourcing/view/project_role.go +++ b/internal/management/repository/eventsourcing/view/project_role.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/project/repository/view" "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -31,20 +32,20 @@ func (v *View) SearchProjectRoles(request *proj_model.ProjectRoleSearchRequest) return view.SearchProjectRoles(v.Db, projectRoleTable, request) } -func (v *View) PutProjectRole(project *model.ProjectRoleView) error { +func (v *View) PutProjectRole(project *model.ProjectRoleView, eventTimestamp time.Time) error { err := view.PutProjectRole(v.Db, projectRoleTable, project) if err != nil { return err } - return v.ProcessedProjectRoleSequence(project.Sequence) + return v.ProcessedProjectRoleSequence(project.Sequence, eventTimestamp) } -func (v *View) DeleteProjectRole(projectID, orgID, key string, eventSequence uint64) error { +func (v *View) DeleteProjectRole(projectID, orgID, key string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteProjectRole(v.Db, projectRoleTable, projectID, orgID, key) if err != nil { return nil } - return v.ProcessedProjectRoleSequence(eventSequence) + return v.ProcessedProjectRoleSequence(eventSequence, eventTimestamp) } func (v *View) DeleteProjectRolesByProjectID(projectID string) error { @@ -55,8 +56,12 @@ func (v *View) GetLatestProjectRoleSequence() (*repository.CurrentSequence, erro return v.latestSequence(projectRoleTable) } -func (v *View) ProcessedProjectRoleSequence(eventSequence uint64) error { - return v.saveCurrentSequence(projectRoleTable, eventSequence) +func (v *View) ProcessedProjectRoleSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(projectRoleTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateProjectRoleSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(projectRoleTable) } func (v *View) GetLatestProjectRoleFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/sequence.go b/internal/management/repository/eventsourcing/view/sequence.go index 40d5338540..82354c98e4 100644 --- a/internal/management/repository/eventsourcing/view/sequence.go +++ b/internal/management/repository/eventsourcing/view/sequence.go @@ -2,16 +2,29 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( sequencesTable = "management.current_sequences" ) -func (v *View) saveCurrentSequence(viewName string, sequence uint64) error { - return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence) +func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimestamp time.Time) error { + return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimestamp) } func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) { return repository.LatestSequence(v.Db, sequencesTable, viewName) } + +func (v *View) updateSpoolerRunSequence(viewName string) error { + currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName) + if err != nil { + return err + } + if currentSequence.ViewName == "" { + currentSequence.ViewName = viewName + } + currentSequence.LastSuccessfulSpoolerRun = time.Now() + return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence) +} diff --git a/internal/management/repository/eventsourcing/view/user.go b/internal/management/repository/eventsourcing/view/user.go index acc327a3cd..69fc14a270 100644 --- a/internal/management/repository/eventsourcing/view/user.go +++ b/internal/management/repository/eventsourcing/view/user.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -35,43 +36,47 @@ func (v *View) IsUserUnique(userName, email string) (bool, error) { return view.IsUserUnique(v.Db, userTable, userName, email) } -func (v *View) UserMfas(userID string) ([]*usr_model.MultiFactor, error) { - return view.UserMfas(v.Db, userTable, userID) +func (v *View) UserMFAs(userID string) ([]*usr_model.MultiFactor, error) { + return view.UserMFAs(v.Db, userTable, userID) } -func (v *View) PutUsers(user []*model.UserView, sequence uint64) error { +func (v *View) PutUsers(user []*model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUsers(v.Db, userTable, user...) if err != nil { return err } - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } -func (v *View) PutUser(user *model.UserView, sequence uint64) error { +func (v *View) PutUser(user *model.UserView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUser(v.Db, userTable, user) if err != nil { return err } if sequence != 0 { - return v.ProcessedUserSequence(sequence) + return v.ProcessedUserSequence(sequence, eventTimestamp) } return nil } -func (v *View) DeleteUser(userID string, eventSequence uint64) error { +func (v *View) DeleteUser(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUser(v.Db, userTable, userID) if err != nil { return nil } - return v.ProcessedUserSequence(eventSequence) + return v.ProcessedUserSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userTable) } -func (v *View) ProcessedUserSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userTable, eventSequence) +func (v *View) ProcessedUserSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userTable) } func (v *View) GetLatestUserFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/user_grant.go b/internal/management/repository/eventsourcing/view/user_grant.go index 29e3a2d33a..209fbfe6f4 100644 --- a/internal/management/repository/eventsourcing/view/user_grant.go +++ b/internal/management/repository/eventsourcing/view/user_grant.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/usergrant/repository/view" "github.com/caos/zitadel/internal/usergrant/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -39,36 +40,40 @@ func (v *View) UserGrantsByOrgIDAndProjectID(orgID, projectID string) ([]*model. return view.UserGrantsByOrgIDAndProjectID(v.Db, userGrantTable, orgID, projectID) } -func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error { +func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserGrant(v.Db, userGrantTable, grant) if err != nil { return err } - return v.ProcessedUserGrantSequence(sequence) + return v.ProcessedUserGrantSequence(sequence, eventTimestamp) } -func (v *View) PutUserGrants(grants []*model.UserGrantView, sequence uint64) error { +func (v *View) PutUserGrants(grants []*model.UserGrantView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserGrants(v.Db, userGrantTable, grants...) if err != nil { return err } - return v.ProcessedUserGrantSequence(sequence) + return v.ProcessedUserGrantSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error { +func (v *View) DeleteUserGrant(grantID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserGrant(v.Db, userGrantTable, grantID) if err != nil { return nil } - return v.ProcessedUserGrantSequence(eventSequence) + return v.ProcessedUserGrantSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserGrantSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userGrantTable) } -func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userGrantTable, eventSequence) +func (v *View) ProcessedUserGrantSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userGrantTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserGrantSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userGrantTable) } func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/eventsourcing/view/user_membership.go b/internal/management/repository/eventsourcing/view/user_membership.go index 8188437187..4c733dc875 100644 --- a/internal/management/repository/eventsourcing/view/user_membership.go +++ b/internal/management/repository/eventsourcing/view/user_membership.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -23,60 +24,64 @@ func (v *View) SearchUserMemberships(request *usr_model.UserMembershipSearchRequ return view.SearchUserMemberships(v.Db, userMembershipTable, request) } -func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64) error { +func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserMembership(v.Db, userMembershipTable, membership) if err != nil { return err } - return v.ProcessedUserMembershipSequence(sequence) + return v.ProcessedUserMembershipSequence(sequence, eventTimestamp) } -func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error { +func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64, eventTimestamp time.Time) error { err := view.PutUserMemberships(v.Db, userMembershipTable, memberships...) if err != nil { return err } - return v.ProcessedUserMembershipSequence(sequence) + return v.ProcessedUserMembershipSequence(sequence, eventTimestamp) } -func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64) error { +func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembership(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByUserID(v.Db, userMembershipTable, userID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByAggregateID(aggregateID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByAggregateID(aggregateID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByAggregateID(v.Db, userMembershipTable, aggregateID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } -func (v *View) DeleteUserMembershipsByAggregateIDAndObjectID(aggregateID, objectID string, eventSequence uint64) error { +func (v *View) DeleteUserMembershipsByAggregateIDAndObjectID(aggregateID, objectID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteUserMembershipsByAggregateIDAndObjectID(v.Db, userMembershipTable, aggregateID, objectID) if err != nil { return nil } - return v.ProcessedUserMembershipSequence(eventSequence) + return v.ProcessedUserMembershipSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userMembershipTable) } -func (v *View) ProcessedUserMembershipSequence(eventSequence uint64) error { - return v.saveCurrentSequence(userMembershipTable, eventSequence) +func (v *View) ProcessedUserMembershipSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(userMembershipTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateUserMembershipSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(userMembershipTable) } func (v *View) GetLatestUserMembershipFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index ae035368e7..92a2364c11 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -30,7 +30,7 @@ type UserRepository interface { ProfileByID(ctx context.Context, userID string) (*model.Profile, error) ChangeProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error) - UserMfas(ctx context.Context, userID string) ([]*model.MultiFactor, error) + UserMFAs(ctx context.Context, userID string) ([]*model.MultiFactor, error) RemoveOTP(ctx context.Context, userID string) error SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go index e591b2fd15..96ff4ae9ce 100644 --- a/internal/notification/repository/eventsourcing/handler/notification.go +++ b/internal/notification/repository/eventsourcing/handler/notification.go @@ -71,12 +71,12 @@ func (n *Notification) Reduce(event *models.Event) (err error) { case es_model.DomainClaimed: err = n.handleDomainClaimed(event) default: - return n.view.ProcessedNotificationSequence(event.Sequence) + return n.view.ProcessedNotificationSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return n.view.ProcessedNotificationSequence(event.Sequence) + return n.view.ProcessedNotificationSequence(event.Sequence, event.CreationDate) } func (n *Notification) handleInitUserCode(event *models.Event) (err error) { @@ -242,6 +242,10 @@ func (n *Notification) OnError(event *models.Event, err error) error { return spooler.HandleError(event, err, n.view.GetLatestNotificationFailedEvent, n.view.ProcessedNotificationFailedEvent, n.view.ProcessedNotificationSequence, n.errorCountUntilSkip) } +func (n *Notification) OnSuccess() error { + return spooler.HandleSuccess(n.view.UpdateNotificationSpoolerRunTimestamp) +} + func getSetNotifyContextData(orgID string) context.Context { return authz.SetCtxData(context.Background(), authz.CtxData{UserID: NotifyUserID, OrgID: orgID}) } diff --git a/internal/notification/repository/eventsourcing/handler/notify_user.go b/internal/notification/repository/eventsourcing/handler/notify_user.go index e05e624d39..b41cd93ce7 100644 --- a/internal/notification/repository/eventsourcing/handler/notify_user.go +++ b/internal/notification/repository/eventsourcing/handler/notify_user.go @@ -93,14 +93,14 @@ func (u *NotifyUser) ProcessUser(event *models.Event) (err error) { } u.fillLoginNames(user) case es_model.UserRemoved: - return u.view.DeleteNotifyUser(event.AggregateID, event.Sequence) + return u.view.DeleteNotifyUser(event.AggregateID, event.Sequence, event.CreationDate) default: - return u.view.ProcessedNotifyUserSequence(event.Sequence) + return u.view.ProcessedNotifyUserSequence(event.Sequence, event.CreationDate) } if err != nil { return err } - return u.view.PutNotifyUser(user, user.Sequence) + return u.view.PutNotifyUser(user, user.Sequence, event.CreationDate) } func (u *NotifyUser) ProcessOrg(event *models.Event) (err error) { @@ -114,7 +114,7 @@ func (u *NotifyUser) ProcessOrg(event *models.Event) (err error) { case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) default: - return u.view.ProcessedNotifyUserSequence(event.Sequence) + return u.view.ProcessedNotifyUserSequence(event.Sequence, event.CreationDate) } } @@ -136,12 +136,12 @@ func (u *NotifyUser) fillLoginNamesOnOrgUsers(event *models.Event) error { } for _, user := range users { user.SetLoginNames(policy, org.Domains) - err := u.view.PutNotifyUser(user, 0) + err := u.view.PutNotifyUser(user, 0, event.CreationDate) if err != nil { return err } } - return u.view.ProcessedNotifyUserSequence(event.Sequence) + return u.view.ProcessedNotifyUserSequence(event.Sequence, event.CreationDate) } func (u *NotifyUser) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error { @@ -165,7 +165,7 @@ func (u *NotifyUser) fillPreferredLoginNamesOnOrgUsers(event *models.Event) erro } for _, user := range users { user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain) - err := u.view.PutNotifyUser(user, 0) + err := u.view.PutNotifyUser(user, 0, event.CreationDate) if err != nil { return err } @@ -194,3 +194,7 @@ func (p *NotifyUser) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-9spwf", "id", event.AggregateID).WithError(err).Warn("something went wrong in notify user handler") return spooler.HandleError(event, err, p.view.GetLatestNotifyUserFailedEvent, p.view.ProcessedNotifyUserFailedEvent, p.view.ProcessedNotifyUserSequence, p.errorCountUntilSkip) } + +func (u *NotifyUser) OnSuccess() error { + return spooler.HandleSuccess(u.view.UpdateNotifyUserSpoolerRunTimestamp) +} diff --git a/internal/notification/repository/eventsourcing/view/notification.go b/internal/notification/repository/eventsourcing/view/notification.go index e44d019ab1..baeb75e162 100644 --- a/internal/notification/repository/eventsourcing/view/notification.go +++ b/internal/notification/repository/eventsourcing/view/notification.go @@ -2,6 +2,7 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -12,8 +13,12 @@ func (v *View) GetLatestNotificationSequence() (*repository.CurrentSequence, err return v.latestSequence(notificationTable) } -func (v *View) ProcessedNotificationSequence(eventSequence uint64) error { - return v.saveCurrentSequence(notificationTable, eventSequence) +func (v *View) ProcessedNotificationSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(notificationTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateNotificationSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(notificationTable) } func (v *View) GetLatestNotificationFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/notification/repository/eventsourcing/view/notify_user.go b/internal/notification/repository/eventsourcing/view/notify_user.go index 1361ff1486..44d1a1e32b 100644 --- a/internal/notification/repository/eventsourcing/view/notify_user.go +++ b/internal/notification/repository/eventsourcing/view/notify_user.go @@ -4,6 +4,7 @@ import ( "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( @@ -14,13 +15,13 @@ func (v *View) NotifyUserByID(userID string) (*model.NotifyUser, error) { return view.NotifyUserByID(v.Db, notifyUserTable, userID) } -func (v *View) PutNotifyUser(user *model.NotifyUser, sequence uint64) error { +func (v *View) PutNotifyUser(user *model.NotifyUser, sequence uint64, eventTimestamp time.Time) error { err := view.PutNotifyUser(v.Db, notifyUserTable, user) if err != nil { return err } if sequence != 0 { - return v.ProcessedNotifyUserSequence(sequence) + return v.ProcessedNotifyUserSequence(sequence, eventTimestamp) } return nil } @@ -29,20 +30,24 @@ func (v *View) NotifyUsersByOrgID(orgID string) ([]*model.NotifyUser, error) { return view.NotifyUsersByOrgID(v.Db, notifyUserTable, orgID) } -func (v *View) DeleteNotifyUser(userID string, eventSequence uint64) error { +func (v *View) DeleteNotifyUser(userID string, eventSequence uint64, eventTimestamp time.Time) error { err := view.DeleteNotifyUser(v.Db, notifyUserTable, userID) if err != nil { return nil } - return v.ProcessedNotifyUserSequence(eventSequence) + return v.ProcessedNotifyUserSequence(eventSequence, eventTimestamp) } func (v *View) GetLatestNotifyUserSequence() (*repository.CurrentSequence, error) { return v.latestSequence(notifyUserTable) } -func (v *View) ProcessedNotifyUserSequence(eventSequence uint64) error { - return v.saveCurrentSequence(notifyUserTable, eventSequence) +func (v *View) ProcessedNotifyUserSequence(eventSequence uint64, eventTimestamp time.Time) error { + return v.saveCurrentSequence(notifyUserTable, eventSequence, eventTimestamp) +} + +func (v *View) UpdateNotifyUserSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(notifyUserTable) } func (v *View) GetLatestNotifyUserFailedEvent(sequence uint64) (*repository.FailedEvent, error) { diff --git a/internal/notification/repository/eventsourcing/view/sequence.go b/internal/notification/repository/eventsourcing/view/sequence.go index 8ad083ab46..f1868e4f44 100644 --- a/internal/notification/repository/eventsourcing/view/sequence.go +++ b/internal/notification/repository/eventsourcing/view/sequence.go @@ -2,16 +2,29 @@ package view import ( "github.com/caos/zitadel/internal/view/repository" + "time" ) const ( sequencesTable = "notification.current_sequences" ) -func (v *View) saveCurrentSequence(viewName string, sequence uint64) error { - return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence) +func (v *View) saveCurrentSequence(viewName string, sequence uint64, eventTimestamp time.Time) error { + return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence, eventTimestamp) } func (v *View) latestSequence(viewName string) (*repository.CurrentSequence, error) { return repository.LatestSequence(v.Db, sequencesTable, viewName) } + +func (v *View) updateSpoolerRunSequence(viewName string) error { + currentSequence, err := repository.LatestSequence(v.Db, sequencesTable, viewName) + if err != nil { + return err + } + if currentSequence.ViewName == "" { + currentSequence.ViewName = viewName + } + currentSequence.LastSuccessfulSpoolerRun = time.Now() + return repository.UpdateCurrentSequence(v.Db, sequencesTable, currentSequence) +} diff --git a/internal/org/repository/eventsourcing/eventstore.go b/internal/org/repository/eventsourcing/eventstore.go index 8db5c785eb..c312c5e808 100644 --- a/internal/org/repository/eventsourcing/eventstore.go +++ b/internal/org/repository/eventsourcing/eventstore.go @@ -905,7 +905,7 @@ func (es *OrgEventstore) AddSecondFactorToLoginPolicy(ctx context.Context, aggre if err != nil { return 0, err } - if _, m := iam_es_model.GetMFA(repoOrg.LoginPolicy.SecondFactors, repoMFA.MfaType); m != 0 { + if _, m := iam_es_model.GetMFA(repoOrg.LoginPolicy.SecondFactors, repoMFA.MFAType); m != 0 { return iam_model.SecondFactorType(m), nil } return 0, errors.ThrowInternal(nil, "EVENT-rM9so", "Errors.Internal") @@ -950,7 +950,7 @@ func (es *OrgEventstore) AddMultiFactorToLoginPolicy(ctx context.Context, aggreg if err != nil { return 0, err } - if _, m := iam_es_model.GetMFA(repoOrg.LoginPolicy.MultiFactors, repoMFA.MfaType); m != 0 { + if _, m := iam_es_model.GetMFA(repoOrg.LoginPolicy.MultiFactors, repoMFA.MFAType); m != 0 { return iam_model.MultiFactorType(m), nil } return 0, errors.ThrowInternal(nil, "EVENT-2fMo0", "Errors.Internal") diff --git a/internal/org/repository/eventsourcing/eventstore_mock_test.go b/internal/org/repository/eventsourcing/eventstore_mock_test.go index f8aa7fb6cb..6d7d175317 100644 --- a/internal/org/repository/eventsourcing/eventstore_mock_test.go +++ b/internal/org/repository/eventsourcing/eventstore_mock_test.go @@ -109,8 +109,8 @@ func GetMockChangesOrgWithLoginPolicyWithMFA(ctrl *gomock.Controller) *OrgEvents orgData, _ := json.Marshal(model.Org{Name: "MusterOrg"}) loginPolicy, _ := json.Marshal(iam_es_model.LoginPolicy{AllowRegister: true, AllowExternalIdp: true, AllowUsernamePassword: true}) idpData, _ := json.Marshal(iam_es_model.IDPProvider{IDPConfigID: "IDPConfigID", Type: int32(iam_model.IDPProviderTypeSystem)}) - secondFactor, _ := json.Marshal(iam_es_model.MFA{MfaType: int32(iam_model.SecondFactorTypeOTP)}) - multiFactor, _ := json.Marshal(iam_es_model.MFA{MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN)}) + secondFactor, _ := json.Marshal(iam_es_model.MFA{MFAType: int32(iam_model.SecondFactorTypeOTP)}) + multiFactor, _ := json.Marshal(iam_es_model.MFA{MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN)}) events := []*es_models.Event{ {AggregateID: "AggregateID", Sequence: 1, Type: model.OrgAdded, Data: orgData}, {AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicyAdded, Data: loginPolicy}, diff --git a/internal/org/repository/eventsourcing/login_policy.go b/internal/org/repository/eventsourcing/login_policy.go index bc25997395..def04a04b0 100644 --- a/internal/org/repository/eventsourcing/login_policy.go +++ b/internal/org/repository/eventsourcing/login_policy.go @@ -105,7 +105,7 @@ func LoginPolicySecondFactorAddedAggregate(aggCreator *es_models.AggregateCreato AggregateTypeFilter(model.OrgAggregate). AggregateIDsFilter(org.AggregateID) - validation := checkExistingLoginPolicySecondFactorValidation(mfa.MfaType) + validation := checkExistingLoginPolicySecondFactorValidation(mfa.MFAType) agg.SetPrecondition(validationQuery, validation) return agg.AppendEvent(model.LoginPolicySecondFactorAdded, mfa) } @@ -137,7 +137,7 @@ func LoginPolicyMultiFactorAddedAggregate(aggCreator *es_models.AggregateCreator AggregateTypeFilter(model.OrgAggregate). AggregateIDsFilter(org.AggregateID) - validation := checkExistingLoginPolicyMultiFactorValidation(mfa.MfaType) + validation := checkExistingLoginPolicyMultiFactorValidation(mfa.MFAType) agg.SetPrecondition(validationQuery, validation) return agg.AppendEvent(model.LoginPolicyMultiFactorAdded, mfa) } @@ -261,7 +261,7 @@ func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_m if err != nil { return err } - mfas = append(mfas, mfa.MfaType) + mfas = append(mfas, mfa.MFAType) case model.LoginPolicySecondFactorRemoved: idp := new(iam_es_model.IDPProvider) err := idp.SetData(event) @@ -301,7 +301,7 @@ func checkExistingLoginPolicyMultiFactorValidation(mfaType int32) func(...*es_mo if err != nil { return err } - mfas = append(mfas, mfa.MfaType) + mfas = append(mfas, mfa.MFAType) case model.LoginPolicyMultiFactorRemoved: idp := new(iam_es_model.IDPProvider) err := idp.SetData(event) diff --git a/internal/org/repository/eventsourcing/login_policy_test.go b/internal/org/repository/eventsourcing/login_policy_test.go index 838b8b28c2..fb11d413cb 100644 --- a/internal/org/repository/eventsourcing/login_policy_test.go +++ b/internal/org/repository/eventsourcing/login_policy_test.go @@ -472,7 +472,7 @@ func TestLoginPolicySecondFactorAddedAggregate(t *testing.T) { ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, }, new: &iam_es_model.MFA{ - MfaType: int32(iam_model.SecondFactorTypeOTP), + MFAType: int32(iam_model.SecondFactorTypeOTP), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -562,7 +562,7 @@ func TestLoginPolicySecondFactorRemovedAggregate(t *testing.T) { }, }}, new: &iam_es_model.MFA{ - MfaType: int32(iam_model.SecondFactorTypeOTP), + MFAType: int32(iam_model.SecondFactorTypeOTP), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -645,7 +645,7 @@ func TestLoginPolicyMultiFactorAddedAggregate(t *testing.T) { ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, }, new: &iam_es_model.MFA{ - MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN), + MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN), }, aggCreator: models.NewAggregateCreator("Test"), }, @@ -735,7 +735,7 @@ func TestLoginPolicyMultiFactorRemovedAggregate(t *testing.T) { }, }}, new: &iam_es_model.MFA{ - MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN), + MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN), }, aggCreator: models.NewAggregateCreator("Test"), }, diff --git a/internal/org/repository/eventsourcing/model/login_policy.go b/internal/org/repository/eventsourcing/model/login_policy.go index ab1ca319ce..02388669f8 100644 --- a/internal/org/repository/eventsourcing/model/login_policy.go +++ b/internal/org/repository/eventsourcing/model/login_policy.go @@ -55,7 +55,7 @@ func (o *Org) appendAddSecondFactorToLoginPolicyEvent(event *es_models.Event) er if err != nil { return err } - o.LoginPolicy.SecondFactors = append(o.LoginPolicy.SecondFactors, mfa.MfaType) + o.LoginPolicy.SecondFactors = append(o.LoginPolicy.SecondFactors, mfa.MFAType) return nil } @@ -65,7 +65,7 @@ func (o *Org) appendRemoveSecondFactorFromLoginPolicyEvent(event *es_models.Even if err != nil { return err } - if i, m := iam_es_model.GetMFA(o.LoginPolicy.SecondFactors, mfa.MfaType); m != 0 { + if i, m := iam_es_model.GetMFA(o.LoginPolicy.SecondFactors, mfa.MFAType); m != 0 { o.LoginPolicy.SecondFactors[i] = o.LoginPolicy.SecondFactors[len(o.LoginPolicy.SecondFactors)-1] o.LoginPolicy.SecondFactors[len(o.LoginPolicy.SecondFactors)-1] = 0 o.LoginPolicy.SecondFactors = o.LoginPolicy.SecondFactors[:len(o.LoginPolicy.SecondFactors)-1] @@ -80,7 +80,7 @@ func (o *Org) appendAddMultiFactorToLoginPolicyEvent(event *es_models.Event) err if err != nil { return err } - o.LoginPolicy.MultiFactors = append(o.LoginPolicy.MultiFactors, mfa.MfaType) + o.LoginPolicy.MultiFactors = append(o.LoginPolicy.MultiFactors, mfa.MFAType) return nil } @@ -90,7 +90,7 @@ func (o *Org) appendRemoveMultiFactorFromLoginPolicyEvent(event *es_models.Event if err != nil { return err } - if i, m := iam_es_model.GetMFA(o.LoginPolicy.MultiFactors, mfa.MfaType); m != 0 { + if i, m := iam_es_model.GetMFA(o.LoginPolicy.MultiFactors, mfa.MFAType); m != 0 { o.LoginPolicy.MultiFactors[i] = o.LoginPolicy.MultiFactors[len(o.LoginPolicy.MultiFactors)-1] o.LoginPolicy.MultiFactors[len(o.LoginPolicy.MultiFactors)-1] = 0 o.LoginPolicy.MultiFactors = o.LoginPolicy.MultiFactors[:len(o.LoginPolicy.MultiFactors)-1] diff --git a/internal/org/repository/eventsourcing/model/login_policy_test.go b/internal/org/repository/eventsourcing/model/login_policy_test.go index 7ef70e275d..2662cb0910 100644 --- a/internal/org/repository/eventsourcing/model/login_policy_test.go +++ b/internal/org/repository/eventsourcing/model/login_policy_test.go @@ -224,7 +224,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { name: "append add second factor to login policy event", args: args{ org: &Org{LoginPolicy: &iam_es_model.LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}}, - mfa: &iam_es_model.MFA{MfaType: int32(iam_model.SecondFactorTypeOTP)}, + mfa: &iam_es_model.MFA{MFAType: int32(iam_model.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ @@ -246,7 +246,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { if len(tt.result.LoginPolicy.SecondFactors) != len(tt.args.org.LoginPolicy.SecondFactors) { t.Errorf("got wrong second factor len: expected: %v, actual: %v ", len(tt.result.LoginPolicy.SecondFactors), len(tt.args.org.LoginPolicy.SecondFactors)) } - if tt.result.LoginPolicy.SecondFactors[0] != tt.args.mfa.MfaType { + if tt.result.LoginPolicy.SecondFactors[0] != tt.args.mfa.MFAType { t.Errorf("got wrong second factor: expected: %v, actual: %v ", tt.result.LoginPolicy.SecondFactors[0], tt.args.mfa) } }) @@ -275,7 +275,7 @@ func TestRemoveSecondFactorFromPolicyEvent(t *testing.T) { SecondFactors: []int32{ int32(iam_model.SecondFactorTypeOTP), }}}, - mfa: &iam_es_model.MFA{MfaType: int32(iam_model.SecondFactorTypeOTP)}, + mfa: &iam_es_model.MFA{MFAType: int32(iam_model.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ @@ -314,7 +314,7 @@ func TestAppendAddMultiFactorToPolicyEvent(t *testing.T) { name: "append add mfa to login policy event", args: args{ org: &Org{LoginPolicy: &iam_es_model.LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}}, - mfa: &iam_es_model.MFA{MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN)}, + mfa: &iam_es_model.MFA{MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ @@ -336,7 +336,7 @@ func TestAppendAddMultiFactorToPolicyEvent(t *testing.T) { if len(tt.result.LoginPolicy.MultiFactors) != len(tt.args.org.LoginPolicy.MultiFactors) { t.Errorf("got wrong second factor len: expected: %v, actual: %v ", len(tt.result.LoginPolicy.MultiFactors), len(tt.args.org.LoginPolicy.MultiFactors)) } - if tt.result.LoginPolicy.MultiFactors[0] != tt.args.mfa.MfaType { + if tt.result.LoginPolicy.MultiFactors[0] != tt.args.mfa.MFAType { t.Errorf("got wrong second factor: expected: %v, actual: %v ", tt.result.LoginPolicy.MultiFactors[0], tt.args.mfa) } }) @@ -365,7 +365,7 @@ func TestRemoveMultiFactorFromPolicyEvent(t *testing.T) { MultiFactors: []int32{ int32(iam_model.MultiFactorTypeU2FWithPIN), }}}, - mfa: &iam_es_model.MFA{MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN)}, + mfa: &iam_es_model.MFA{MFAType: int32(iam_model.MultiFactorTypeU2FWithPIN)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ diff --git a/internal/project/repository/eventsourcing/eventstore.go b/internal/project/repository/eventsourcing/eventstore.go index d9b3f861bb..8967c81389 100644 --- a/internal/project/repository/eventsourcing/eventstore.go +++ b/internal/project/repository/eventsourcing/eventstore.go @@ -20,6 +20,7 @@ import ( "github.com/caos/zitadel/internal/id" proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( @@ -788,7 +789,9 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal") } -func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) error { +func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() if appID == "" { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-H3RT2", "Errors.Project.RequiredFieldsMissing") } @@ -804,7 +807,10 @@ func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, project return caos_errs.ThrowPreconditionFailed(nil, "EVENT-huywq", "Errors.Project.AppIsNotOIDC") } - if err := crypto.CompareHash(app.OIDCConfig.ClientSecret, []byte(secret), es.passwordAlg); err == nil { + ctx, spanHash := tracing.NewSpan(ctx) + err = crypto.CompareHash(app.OIDCConfig.ClientSecret, []byte(secret), es.passwordAlg) + spanHash.EndWithError(err) + if err == nil { return es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckSucceededAggregate) } if err := es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckFailedAggregate); err != nil { diff --git a/internal/setup/config.go b/internal/setup/config.go index 423b999695..93e0d4926b 100644 --- a/internal/setup/config.go +++ b/internal/setup/config.go @@ -13,6 +13,7 @@ type IAMSetUp struct { Step5 *Step5 Step6 *Step6 Step7 *Step7 + Step8 *Step8 } func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) { @@ -27,6 +28,7 @@ func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) { setup.Step5, setup.Step6, setup.Step7, + setup.Step8, } { if step.step() <= currentDone { continue diff --git a/internal/setup/step7.go b/internal/setup/step7.go index 210fa8fb56..99d445911c 100644 --- a/internal/setup/step7.go +++ b/internal/setup/step7.go @@ -32,23 +32,23 @@ func (step *Step7) init(setup *Setup) { func (step *Step7) execute(ctx context.Context) (*iam_model.IAM, error) { iam, agg, err := step.add2FAToPolicy(ctx, step.DefaultSecondFactor) if err != nil { - logging.Log("SETUP-ZTuS1").WithField("step", step.step()).WithError(err).Error("unable to finish setup (add default mfa to login policy)") + logging.Log("SETUP-GBD32").WithField("step", step.step()).WithError(err).Error("unable to finish setup (add default mfa to login policy)") return nil, err } iam, agg, push, err := step.setup.IamEvents.PrepareSetupDone(ctx, iam, agg, step.step()) if err != nil { - logging.Log("SETUP-OkF8o").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)") + logging.Log("SETUP-BHrth").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)") return nil, err } err = es_sdk.PushAggregates(ctx, push, iam.AppendEvents, agg) if err != nil { - logging.Log("SETUP-YbQ6T").WithField("step", step.step()).WithError(err).Error("unable to finish setup") + logging.Log("SETUP-k2fla").WithField("step", step.step()).WithError(err).Error("unable to finish setup") return nil, err } return iam_es_model.IAMToModel(iam), nil } func (step *Step7) add2FAToPolicy(ctx context.Context, secondFactor iam_model.SecondFactorType) (*iam_es_model.IAM, *models.Aggregate, error) { - logging.Log("SETUP-geMGDuZ").Info("adding 2FA to loginPolicy") + logging.Log("SETUP-Bew1a").Info("adding 2FA to loginPolicy") return step.setup.IamEvents.PrepareAddSecondFactorToLoginPolicy(ctx, step.setup.iamID, secondFactor) } diff --git a/internal/setup/step8.go b/internal/setup/step8.go new file mode 100644 index 0000000000..30f70a41fb --- /dev/null +++ b/internal/setup/step8.go @@ -0,0 +1,54 @@ +package setup + +import ( + "context" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore/models" + es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" + iam_model "github.com/caos/zitadel/internal/iam/model" + iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" +) + +type Step8 struct { + DefaultSecondFactor iam_model.SecondFactorType + + setup *Setup +} + +func (step *Step8) isNil() bool { + return step == nil +} + +func (step *Step8) step() iam_model.Step { + return iam_model.Step8 +} + +func (step *Step8) init(setup *Setup) { + step.setup = setup +} + +func (step *Step8) execute(ctx context.Context) (*iam_model.IAM, error) { + iam, agg, err := step.add2FAToPolicy(ctx, step.DefaultSecondFactor) + if err != nil { + logging.Log("SETUP-Gdbjq").WithField("step", step.step()).WithError(err).Error("unable to finish setup (add default mfa to login policy)") + return nil, err + } + iam, agg, push, err := step.setup.IamEvents.PrepareSetupDone(ctx, iam, agg, step.step()) + if err != nil { + logging.Log("SETUP-Cnf21").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)") + return nil, err + } + err = es_sdk.PushAggregates(ctx, push, iam.AppendEvents, agg) + if err != nil { + logging.Log("SETUP-NFq21").WithField("step", step.step()).WithError(err).Error("unable to finish setup") + return nil, err + } + return iam_es_model.IAMToModel(iam), nil +} + +func (step *Step8) add2FAToPolicy(ctx context.Context, secondFactor iam_model.SecondFactorType) (*iam_es_model.IAM, *models.Aggregate, error) { + logging.Log("SETUP-Bfhb2").Info("adding 2FA to loginPolicy") + return step.setup.IamEvents.PrepareAddSecondFactorToLoginPolicy(ctx, step.setup.iamID, secondFactor) +} diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index a32ac9f4e3..729e781d98 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -53,12 +53,25 @@ Errors: IDPConfigNotExisting: IDP Provider ungültig für diese Organisation NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt. MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden. - Mfa: - Otp: + MFA: + OTP: AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet NotExisting: Multifaktor OTP (OneTimePassword) existiert nicht NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit InvalidCode: Code ist ungültig + U2F: + NotExisting: U2F existiert nicht + Passwordless: + NotExisting: Passwortlos existiert nicht + WebAuthN: + NotFound: WebAuthN Token konnte nicht gefunden werden + BeginRegisterFailed: Es ist ein Fehler bei der WebAuthN Registrierung aufgetreten + MarshalError: Daten konnten nicht umgewandelt werden + ErrorOnParseCredential: Zugangsdaten konnten nicht geparsed werden + CreateCredentialFailed: Zugangsdaten konnten nicht gespeichert werden + BeginLoginFailed: Es ist ein Fehler beim WebAuthN Login aufgetreten + ValidateLoginFailed: Zugangsdaten konnten nicht validiert werden + CloneWarning: Authentifizierungsdaten wurden möglicherweise geklont Org: Invalid: Organisation ist ungültig AlreadyDeactivated: Organisation ist bereits deaktiviert diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 08579d24bf..79088a8465 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -53,12 +53,25 @@ Errors: IDPConfigNotExisting: IDP provider invalid for this organisation NotAllowed: External IDP not allowed on this organisation MinimumExternalIDPNeeded: At least one IDP must be added - Mfa: - Otp: + MFA: + OTP: AlreadyReady: Multifactor OTP (OneTimePassword) is already set up NotExisting: Multifactor OTP (OneTimePassword) doesn't exist NotReady: Multifactor OTP (OneTimePassword) isn't ready InvalidCode: Invalid code + U2F: + NotExisting: U2F does not exist + Passwordless: + NotExisting: Passwordless does not exist + WebAuthN: + NotFound: WebAuthN Token could not be found + BeginRegisterFailed: WebAuthN begin registration failed + MarshalError: Error on marshal data + ErrorOnParseCredential: Error on parse credential data + CreateCredentialFailed: Error on create credentials + BeginLoginFailed: WebAuthN begin login failed + ValidateLoginFailed: Error on validate login credentials + CloneWarning: Credentials may be cloned Org: Invalid: Organisation is invalid AlreadyDeactivated: Organisation is already deactivated diff --git a/internal/telemetry/http_handler.go b/internal/telemetry/http_handler.go new file mode 100644 index 0000000000..6985791954 --- /dev/null +++ b/internal/telemetry/http_handler.go @@ -0,0 +1,33 @@ +package telemetry + +import ( + "github.com/caos/zitadel/internal/telemetry/metrics" + "net/http" + "strings" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +func shouldNotIgnore(endpoints ...string) func(r *http.Request) bool { + return func(r *http.Request) bool { + for _, endpoint := range endpoints { + if strings.HasPrefix(r.URL.RequestURI(), endpoint) { + return false + } + } + return true + } +} + +func TelemetryHandler(handler http.Handler, ignoredEndpoints ...string) http.Handler { + return otelhttp.NewHandler(handler, + "zitadel", + otelhttp.WithFilter(shouldNotIgnore(ignoredEndpoints...)), + otelhttp.WithPublicEndpoint(), + otelhttp.WithSpanNameFormatter(spanNameFormatter), + otelhttp.WithMeterProvider(metrics.GetMetricsProvider())) +} + +func spanNameFormatter(_ string, r *http.Request) string { + return r.Host + r.URL.EscapedPath() +} diff --git a/internal/telemetry/metrics/config/config.go b/internal/telemetry/metrics/config/config.go new file mode 100644 index 0000000000..50bfe3df47 --- /dev/null +++ b/internal/telemetry/metrics/config/config.go @@ -0,0 +1,65 @@ +package config + +import ( + "encoding/json" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/metrics" + "github.com/caos/zitadel/internal/telemetry/metrics/otel" +) + +type MetricsConfig struct { + Type string + Config metrics.Config +} + +var meter = map[string]func() metrics.Config{ + "otel": func() metrics.Config { return &otel.Config{} }, + "none": func() metrics.Config { return &NoMetrics{} }, + "": func() metrics.Config { return &NoMetrics{} }, +} + +func (c *MetricsConfig) UnmarshalJSON(data []byte) error { + var rc struct { + Type string + Config json.RawMessage + } + + if err := json.Unmarshal(data, &rc); err != nil { + return errors.ThrowInternal(err, "METER-4M9so", "error parsing config") + } + + c.Type = rc.Type + + var err error + c.Config, err = newMetricsConfig(c.Type, rc.Config) + if err != nil { + return err + } + + return c.Config.NewMetrics() +} + +func newMetricsConfig(tracerType string, configData []byte) (metrics.Config, error) { + t, ok := meter[tracerType] + if !ok { + return nil, errors.ThrowInternalf(nil, "METER-3M0ps", "config type %s not supported", tracerType) + } + + metricsConfig := t() + if len(configData) == 0 { + return metricsConfig, nil + } + + if err := json.Unmarshal(configData, metricsConfig); err != nil { + return nil, errors.ThrowInternal(err, "METER-4M9sf", "Could not read config: %v") + } + + return metricsConfig, nil +} + +type NoMetrics struct{} + +func (_ *NoMetrics) NewMetrics() error { + return nil +} diff --git a/internal/telemetry/metrics/http_handler.go b/internal/telemetry/metrics/http_handler.go new file mode 100644 index 0000000000..e9e86e1df6 --- /dev/null +++ b/internal/telemetry/metrics/http_handler.go @@ -0,0 +1,128 @@ +package metrics + +import ( + "net/http" + "strings" +) + +const ( + RequestCounter = "http.server.request_count" + RequestCountDescription = "Request counter" + TotalRequestCounter = "http.server.total_request_count" + TotalRequestDescription = "Total return code counter" + ReturnCodeCounter = "http.server.return_code_counter" + ReturnCodeCounterDescription = "Return code counter" + Method = "method" + URI = "uri" + ReturnCode = "return_code" +) + +type Handler struct { + handler http.Handler + methods []MetricType + filters []Filter +} + +type MetricType int32 + +const ( + MetricTypeTotalCount MetricType = iota + MetricTypeStatusCode + MetricTypeRequestCount +) + +type StatusRecorder struct { + http.ResponseWriter + Status int +} + +func (r *StatusRecorder) WriteHeader(status int) { + r.Status = status + r.ResponseWriter.WriteHeader(status) +} + +type Filter func(*http.Request) bool + +func NewMetricsHandler(handler http.Handler, metricMethods []MetricType, ignoredEndpoints ...string) http.Handler { + h := Handler{ + handler: handler, + methods: metricMethods, + } + if len(ignoredEndpoints) > 0 { + h.filters = append(h.filters, shouldNotIgnore(ignoredEndpoints...)) + } + return &h +} + +// ServeHTTP serves HTTP requests (http.Handler) +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if len(h.methods) == 0 { + h.handler.ServeHTTP(w, r) + return + } + for _, f := range h.filters { + if !f(r) { + // Simply pass through to the handler if a filter rejects the request + h.handler.ServeHTTP(w, r) + return + } + } + recorder := &StatusRecorder{ + ResponseWriter: w, + Status: 200, + } + h.handler.ServeHTTP(recorder, r) + if h.containsMetricsMethod(MetricTypeRequestCount) { + RegisterRequestCounter(r) + } + if h.containsMetricsMethod(MetricTypeTotalCount) { + RegisterTotalRequestCounter(r) + } + if h.containsMetricsMethod(MetricTypeStatusCode) { + RegisterRequestCodeCounter(recorder, r) + } +} + +func (h *Handler) containsMetricsMethod(method MetricType) bool { + for _, m := range h.methods { + if m == method { + return true + } + } + return false +} + +func RegisterRequestCounter(r *http.Request) { + var labels = map[string]interface{}{ + URI: strings.Split(r.RequestURI, "?")[0], + Method: r.Method, + } + RegisterCounter(RequestCounter, RequestCountDescription) + AddCount(r.Context(), RequestCounter, 1, labels) +} + +func RegisterTotalRequestCounter(r *http.Request) { + RegisterCounter(TotalRequestCounter, TotalRequestDescription) + AddCount(r.Context(), TotalRequestCounter, 1, nil) +} + +func RegisterRequestCodeCounter(recorder *StatusRecorder, r *http.Request) { + var labels = map[string]interface{}{ + URI: strings.Split(r.RequestURI, "?")[0], + Method: r.Method, + ReturnCode: recorder.Status, + } + RegisterCounter(ReturnCodeCounter, ReturnCodeCounterDescription) + AddCount(r.Context(), ReturnCodeCounter, 1, labels) +} + +func shouldNotIgnore(endpoints ...string) func(r *http.Request) bool { + return func(r *http.Request) bool { + for _, endpoint := range endpoints { + if strings.HasPrefix(r.URL.RequestURI(), endpoint) { + return false + } + } + return true + } +} diff --git a/internal/telemetry/metrics/metrics.go b/internal/telemetry/metrics/metrics.go new file mode 100644 index 0000000000..4dc3b0675d --- /dev/null +++ b/internal/telemetry/metrics/metrics.go @@ -0,0 +1,73 @@ +package metrics + +import ( + "context" + "go.opentelemetry.io/otel/api/metric" + "net/http" +) + +const ( + ActiveSessionCounter = "zitadel.active_session_counter" + ActiveSessionCounterDescription = "Active session counter" + SpoolerDivCounter = "zitadel.spooler_div_milliseconds" + SpoolerDivCounterDescription = "Spooler div from last successful run to now in milliseconds" + Database = "database" + ViewName = "view_name" +) + +type Metrics interface { + GetExporter() http.Handler + GetMetricsProvider() metric.MeterProvider + RegisterCounter(name, description string) error + AddCount(ctx context.Context, name string, value int64, labels map[string]interface{}) error + RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error + RegisterValueObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error +} + +type Config interface { + NewMetrics() error +} + +var M Metrics + +func GetExporter() http.Handler { + if M == nil { + return nil + } + return M.GetExporter() +} + +func GetMetricsProvider() metric.MeterProvider { + if M == nil { + return nil + } + return M.GetMetricsProvider() +} + +func RegisterCounter(name, description string) error { + if M == nil { + return nil + } + return M.RegisterCounter(name, description) +} + +func AddCount(ctx context.Context, name string, value int64, labels map[string]interface{}) error { + if M == nil { + return nil + } + return M.AddCount(ctx, name, value, labels) +} + +func RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error { + if M == nil { + return nil + } + return M.RegisterUpDownSumObserver(name, description, callbackFunc) +} + +func RegisterValueObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error { + if M == nil { + return nil + } + return M.RegisterValueObserver(name, description, callbackFunc) +} diff --git a/internal/telemetry/metrics/otel/config.go b/internal/telemetry/metrics/otel/config.go new file mode 100644 index 0000000000..8d6718d319 --- /dev/null +++ b/internal/telemetry/metrics/otel/config.go @@ -0,0 +1,14 @@ +package otel + +import ( + "github.com/caos/zitadel/internal/telemetry/metrics" +) + +type Config struct { + MeterName string +} + +func (c *Config) NewMetrics() (err error) { + metrics.M, err = NewMetrics(c.MeterName) + return err +} diff --git a/internal/telemetry/metrics/otel/open_telemetry.go b/internal/telemetry/metrics/otel/open_telemetry.go new file mode 100644 index 0000000000..80e04f5ce2 --- /dev/null +++ b/internal/telemetry/metrics/otel/open_telemetry.go @@ -0,0 +1,92 @@ +package otel + +import ( + "context" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/metrics" + "go.opentelemetry.io/otel/api/metric" + "go.opentelemetry.io/otel/exporters/metric/prometheus" + "go.opentelemetry.io/otel/label" + "net/http" + "sync" +) + +type Metrics struct { + Exporter *prometheus.Exporter + Meter metric.Meter + Counters sync.Map + UpDownSumObserver sync.Map + ValueObservers sync.Map +} + +func NewMetrics(meterName string) (metrics.Metrics, error) { + exporter, err := prometheus.NewExportPipeline( + prometheus.Config{}, + ) + if err != nil { + return &Metrics{}, err + } + return &Metrics{ + Exporter: exporter, + Meter: exporter.MeterProvider().Meter(meterName), + }, nil +} + +func (m *Metrics) GetExporter() http.Handler { + return m.Exporter +} + +func (m *Metrics) GetMetricsProvider() metric.MeterProvider { + return m.Exporter.MeterProvider() +} + +func (m *Metrics) RegisterCounter(name, description string) error { + if _, exists := m.Counters.Load(name); exists { + return nil + } + counter := metric.Must(m.Meter).NewInt64Counter(name, metric.WithDescription(description)) + m.Counters.Store(name, counter) + return nil +} + +func (m *Metrics) AddCount(ctx context.Context, name string, value int64, labels map[string]interface{}) error { + counter, exists := m.Counters.Load(name) + if !exists { + return caos_errs.ThrowNotFound(nil, "METER-4u8fs", "Errors.Metrics.Counter.NotFound") + } + counter.(metric.Int64Counter).Add(ctx, value, MapToKeyValue(labels)...) + return nil +} + +func (m *Metrics) RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error { + if _, exists := m.UpDownSumObserver.Load(name); exists { + return nil + } + sumObserver := metric.Must(m.Meter).NewInt64UpDownSumObserver( + name, callbackFunc, metric.WithDescription(description)) + + m.UpDownSumObserver.Store(name, sumObserver) + return nil +} + +func (m *Metrics) RegisterValueObserver(name, description string, callbackFunc metric.Int64ObserverFunc) error { + if _, exists := m.UpDownSumObserver.Load(name); exists { + return nil + } + sumObserver := metric.Must(m.Meter).NewInt64ValueObserver( + name, callbackFunc, metric.WithDescription(description)) + + m.UpDownSumObserver.Store(name, sumObserver) + return nil +} + +func MapToKeyValue(labels map[string]interface{}) []label.KeyValue { + if labels == nil { + return nil + } + keyValues := make([]label.KeyValue, 0, len(labels)) + for key, value := range labels { + keyValues = append(keyValues, label.Any(key, value)) + } + return keyValues +} diff --git a/internal/tracing/caller.go b/internal/telemetry/tracing/caller.go similarity index 100% rename from internal/tracing/caller.go rename to internal/telemetry/tracing/caller.go diff --git a/internal/tracing/config/config.go b/internal/telemetry/tracing/config/config.go similarity index 86% rename from internal/tracing/config/config.go rename to internal/telemetry/tracing/config/config.go index 977f8f9057..70a08be426 100644 --- a/internal/tracing/config/config.go +++ b/internal/telemetry/tracing/config/config.go @@ -4,10 +4,10 @@ import ( "encoding/json" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" - "github.com/caos/zitadel/internal/tracing/google" - "github.com/caos/zitadel/internal/tracing/log" - "github.com/caos/zitadel/internal/tracing/otel" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing/google" + "github.com/caos/zitadel/internal/telemetry/tracing/log" + "github.com/caos/zitadel/internal/telemetry/tracing/otel" ) type TracingConfig struct { diff --git a/internal/tracing/google/google_tracer.go b/internal/telemetry/tracing/google/google_tracer.go similarity index 89% rename from internal/tracing/google/google_tracer.go rename to internal/telemetry/tracing/google/google_tracer.go index ba57958730..35754a284e 100644 --- a/internal/tracing/google/google_tracer.go +++ b/internal/telemetry/tracing/google/google_tracer.go @@ -6,8 +6,8 @@ import ( texporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/tracing" - "github.com/caos/zitadel/internal/tracing/otel" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing/otel" sdk_trace "go.opentelemetry.io/otel/sdk/trace" ) diff --git a/internal/tracing/log/config.go b/internal/telemetry/tracing/log/config.go similarity index 82% rename from internal/tracing/log/config.go rename to internal/telemetry/tracing/log/config.go index c903a46f14..6fd34559b6 100644 --- a/internal/tracing/log/config.go +++ b/internal/telemetry/tracing/log/config.go @@ -1,8 +1,8 @@ package log import ( - "github.com/caos/zitadel/internal/tracing" - "github.com/caos/zitadel/internal/tracing/otel" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing/otel" "go.opentelemetry.io/otel/exporters/stdout" sdk_trace "go.opentelemetry.io/otel/sdk/trace" ) diff --git a/internal/tracing/otel/config.go b/internal/telemetry/tracing/otel/config.go similarity index 90% rename from internal/tracing/otel/config.go rename to internal/telemetry/tracing/otel/config.go index 6f0087dc13..9aa7d43d63 100644 --- a/internal/tracing/otel/config.go +++ b/internal/telemetry/tracing/otel/config.go @@ -1,7 +1,7 @@ package otel import ( - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "go.opentelemetry.io/otel/exporters/otlp" sdk_trace "go.opentelemetry.io/otel/sdk/trace" ) diff --git a/internal/tracing/otel/open_telemetry.go b/internal/telemetry/tracing/otel/open_telemetry.go similarity index 97% rename from internal/tracing/otel/open_telemetry.go rename to internal/telemetry/tracing/otel/open_telemetry.go index 12e7b82e72..4524cd0424 100644 --- a/internal/tracing/otel/open_telemetry.go +++ b/internal/telemetry/tracing/otel/open_telemetry.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" "go.opentelemetry.io/otel/api/global" api_trace "go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/propagators" diff --git a/internal/tracing/span.go b/internal/telemetry/tracing/span.go similarity index 100% rename from internal/tracing/span.go rename to internal/telemetry/tracing/span.go diff --git a/internal/tracing/tracing.go b/internal/telemetry/tracing/tracing.go similarity index 100% rename from internal/tracing/tracing.go rename to internal/telemetry/tracing/tracing.go diff --git a/internal/tracing/http_handler.go b/internal/tracing/http_handler.go deleted file mode 100644 index 38c9664a70..0000000000 --- a/internal/tracing/http_handler.go +++ /dev/null @@ -1,31 +0,0 @@ -package tracing - -import ( - "net/http" - "strings" - - http_trace "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -func shouldNotIgnore(endpoints ...string) func(r *http.Request) bool { - return func(r *http.Request) bool { - for _, endpoint := range endpoints { - if strings.HasPrefix(r.URL.RequestURI(), endpoint) { - return false - } - } - return true - } -} - -func TraceHandler(handler http.Handler, ignoredEndpoints ...string) http.Handler { - return http_trace.NewHandler(handler, - "zitadel", - http_trace.WithFilter(shouldNotIgnore(ignoredEndpoints...)), - http_trace.WithPublicEndpoint(), - http_trace.WithSpanNameFormatter(spanNameFormatter)) -} - -func spanNameFormatter(_ string, r *http.Request) string { - return r.Host + r.URL.EscapedPath() -} diff --git a/internal/ui/login/handler/login.go b/internal/ui/login/handler/login.go index 149167c418..7da2ea9678 100644 --- a/internal/ui/login/handler/login.go +++ b/internal/ui/login/handler/login.go @@ -2,7 +2,6 @@ package handler import ( "context" - "github.com/caos/zitadel/internal/config/systemdefaults" "net" "net/http" @@ -16,6 +15,7 @@ import ( "github.com/caos/zitadel/internal/api/http/middleware" auth_repository "github.com/caos/zitadel/internal/auth/repository" "github.com/caos/zitadel/internal/auth/repository/eventsourcing" + "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/form" "github.com/caos/zitadel/internal/id" @@ -82,7 +82,7 @@ func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefa security := middleware.SecurityHeaders(csp(), login.cspErrorHandler) userAgentCookie, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode) logging.Log("CONFI-Dvwf2").OnError(err).Panic("unable to create userAgentInterceptor") - login.router = CreateRouter(login, statikFS, csrf, cache, security, userAgentCookie, middleware.TraceHandler(EndpointResources)) + login.router = CreateRouter(login, statikFS, csrf, cache, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources)) login.renderer = CreateRenderer(prefix, statikFS, config.LanguageCookieName, config.DefaultLanguage) login.parser = form.NewParser() return login, handlerPrefix diff --git a/internal/ui/login/handler/mfa_init_done_handler.go b/internal/ui/login/handler/mfa_init_done_handler.go index 69f41af2f5..741a010677 100644 --- a/internal/ui/login/handler/mfa_init_done_handler.go +++ b/internal/ui/login/handler/mfa_init_done_handler.go @@ -7,15 +7,15 @@ import ( ) const ( - tmplMfaInitDone = "mfainitdone" + tmplMFAInitDone = "mfainitdone" ) type mfaInitDoneData struct { } -func (l *Login) renderMfaInitDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaDoneData) { +func (l *Login) renderMFAInitDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaDoneData) { var errType, errMessage string - data.baseData = l.getBaseData(r, authReq, "Mfa Init Done", errType, errMessage) + data.baseData = l.getBaseData(r, authReq, "MFA Init Done", errType, errMessage) data.profileData = l.getProfileData(authReq) - l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaInitDone], data, nil) + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAInitDone], data, nil) } diff --git a/internal/ui/login/handler/mfa_init_u2f.go b/internal/ui/login/handler/mfa_init_u2f.go new file mode 100644 index 0000000000..dccb3a84d4 --- /dev/null +++ b/internal/ui/login/handler/mfa_init_u2f.go @@ -0,0 +1,59 @@ +package handler + +import ( + "encoding/base64" + "net/http" + + "github.com/caos/zitadel/internal/auth_request/model" + user_model "github.com/caos/zitadel/internal/user/model" +) + +const ( + tmplMFAU2FInit = "mfainitu2f" +) + +func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { + var errType, errMessage, credentialData string + var u2f *user_model.WebAuthNToken + if err == nil { + u2f, err = l.authRepo.AddMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) + } + if err != nil { + errMessage = l.getErrorMessage(r, err) + } + if u2f != nil { + credentialData = base64.RawURLEncoding.EncodeToString(u2f.CredentialCreationData) + } + data := &webAuthNData{ + userData: l.getUserData(r, authReq, "Register WebAuthNToken", errType, errMessage), + CredentialCreationData: credentialData, + } + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAU2FInit], data, nil) +} + +func (l *Login) handleRegisterU2F(w http.ResponseWriter, r *http.Request) { + data := new(webAuthNFormData) + authReq, err := l.getAuthRequestAndParseData(r, data) + if err != nil { + l.renderError(w, r, authReq, err) + return + } + if data.Recreate { + l.renderRegisterU2F(w, r, authReq, nil) + return + } + credData, err := base64.URLEncoding.DecodeString(data.CredentialData) + if err != nil { + l.renderRegisterU2F(w, r, authReq, err) + return + } + + if err = l.authRepo.VerifyMFAU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Name, credData); err != nil { + l.renderRegisterU2F(w, r, authReq, err) + return + } + done := &mfaDoneData{ + MFAType: model.MFATypeU2F, + } + l.renderMFAInitDone(w, r, authReq, done) +} diff --git a/internal/ui/login/handler/mfa_init_verify_handler.go b/internal/ui/login/handler/mfa_init_verify_handler.go index d043d90ea1..bc07c6ede2 100644 --- a/internal/ui/login/handler/mfa_init_verify_handler.go +++ b/internal/ui/login/handler/mfa_init_verify_handler.go @@ -12,17 +12,17 @@ import ( ) const ( - tmplMfaInitVerify = "mfainitverify" + tmplMFAInitVerify = "mfainitverify" ) type mfaInitVerifyData struct { - MfaType model.MFAType `schema:"mfaType"` + MFAType model.MFAType `schema:"mfaType"` Code string `schema:"code"` URL string `schema:"url"` Secret string `schema:"secret"` } -func (l *Login) handleMfaInitVerify(w http.ResponseWriter, r *http.Request) { +func (l *Login) handleMFAInitVerify(w http.ResponseWriter, r *http.Request) { data := new(mfaInitVerifyData) authReq, err := l.getAuthRequestAndParseData(r, data) if err != nil { @@ -30,29 +30,29 @@ func (l *Login) handleMfaInitVerify(w http.ResponseWriter, r *http.Request) { return } var verifyData *mfaVerifyData - switch data.MfaType { + switch data.MFAType { case model.MFATypeOTP: - verifyData = l.handleOtpVerify(w, r, authReq, data) + verifyData = l.handleOTPVerify(w, r, authReq, data) } if verifyData != nil { - l.renderMfaInitVerify(w, r, authReq, verifyData, err) + l.renderMFAInitVerify(w, r, authReq, verifyData, err) return } done := &mfaDoneData{ - MfaType: data.MfaType, + MFAType: data.MFAType, } - l.renderMfaInitDone(w, r, authReq, done) + l.renderMFAInitDone(w, r, authReq, done) } -func (l *Login) handleOtpVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData { - err := l.authRepo.VerifyMfaOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code) +func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData { + err := l.authRepo.VerifyMFAOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code) if err == nil { return nil } mfadata := &mfaVerifyData{ - MfaType: data.MfaType, + MFAType: data.MFAType, otpData: otpData{ Secret: data.Secret, Url: data.URL, @@ -62,21 +62,21 @@ func (l *Login) handleOtpVerify(w http.ResponseWriter, r *http.Request, authReq return mfadata } -func (l *Login) renderMfaInitVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData, err error) { +func (l *Login) renderMFAInitVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) } - data.baseData = l.getBaseData(r, authReq, "Mfa Init Verify", errType, errMessage) + data.baseData = l.getBaseData(r, authReq, "MFA Init Verify", errType, errMessage) data.profileData = l.getProfileData(authReq) - if data.MfaType == model.MFATypeOTP { + if data.MFAType == model.MFATypeOTP { code, err := generateQrCode(data.otpData.Url) if err == nil { data.otpData.QrCode = code } } - l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaInitVerify], data, nil) + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAInitVerify], data, nil) } func generateQrCode(url string) (string, error) { diff --git a/internal/ui/login/handler/mfa_prompt_handler.go b/internal/ui/login/handler/mfa_prompt_handler.go index 516d1d04a3..32a32a00db 100644 --- a/internal/ui/login/handler/mfa_prompt_handler.go +++ b/internal/ui/login/handler/mfa_prompt_handler.go @@ -8,15 +8,15 @@ import ( ) const ( - tmplMfaPrompt = "mfaprompt" + tmplMFAPrompt = "mfaprompt" ) type mfaPromptData struct { - MfaProvider model.MFAType `schema:"provider"` + MFAProvider model.MFAType `schema:"provider"` Skip bool `schema:"skip"` } -func (l *Login) handleMfaPrompt(w http.ResponseWriter, r *http.Request) { +func (l *Login) handleMFAPrompt(w http.ResponseWriter, r *http.Request) { data := new(mfaPromptData) authReq, err := l.getAuthRequestAndParseData(r, data) if err != nil { @@ -25,11 +25,11 @@ func (l *Login) handleMfaPrompt(w http.ResponseWriter, r *http.Request) { } if !data.Skip { mfaVerifyData := new(mfaVerifyData) - mfaVerifyData.MfaType = data.MfaProvider - l.handleMfaCreation(w, r, authReq, mfaVerifyData) + mfaVerifyData.MFAType = data.MFAProvider + l.handleMFACreation(w, r, authReq, mfaVerifyData) return } - err = l.authRepo.SkipMfaInit(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) + err = l.authRepo.SkipMFAInit(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) if err != nil { l.renderError(w, r, authReq, err) return @@ -37,7 +37,7 @@ func (l *Login) handleMfaPrompt(w http.ResponseWriter, r *http.Request) { l.handleLogin(w, r) } -func (l *Login) handleMfaPromptSelection(w http.ResponseWriter, r *http.Request) { +func (l *Login) handleMFAPromptSelection(w http.ResponseWriter, r *http.Request) { data := new(mfaPromptData) authReq, err := l.getAuthRequestAndParseData(r, data) if err != nil { @@ -48,45 +48,48 @@ func (l *Login) handleMfaPromptSelection(w http.ResponseWriter, r *http.Request) l.renderNextStep(w, r, authReq) } -func (l *Login) renderMfaPrompt(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, mfaPromptData *model.MfaPromptStep, err error) { +func (l *Login) renderMFAPrompt(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, mfaPromptData *model.MFAPromptStep, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) } data := mfaData{ - baseData: l.getBaseData(r, authReq, "Mfa Prompt", errType, errMessage), + baseData: l.getBaseData(r, authReq, "MFA Prompt", errType, errMessage), profileData: l.getProfileData(authReq), } if mfaPromptData == nil { - l.renderError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-XU0tj", "Errors.User.Mfa.NoProviders")) + l.renderError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-XU0tj", "Errors.User.MFA.NoProviders")) return } - data.MfaProviders = mfaPromptData.MfaProviders - data.MfaRequired = mfaPromptData.Required + data.MFAProviders = mfaPromptData.MFAProviders + data.MFARequired = mfaPromptData.Required - if len(mfaPromptData.MfaProviders) == 1 && mfaPromptData.Required { + if len(mfaPromptData.MFAProviders) == 1 && mfaPromptData.Required { data := &mfaVerifyData{ - MfaType: mfaPromptData.MfaProviders[0], + MFAType: mfaPromptData.MFAProviders[0], } - l.handleMfaCreation(w, r, authReq, data) + l.handleMFACreation(w, r, authReq, data) return } - l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaPrompt], data, nil) + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAPrompt], data, nil) } -func (l *Login) handleMfaCreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { - switch data.MfaType { +func (l *Login) handleMFACreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { + switch data.MFAType { case model.MFATypeOTP: - l.handleOtpCreation(w, r, authReq, data) + l.handleOTPCreation(w, r, authReq, data) + return + case model.MFATypeU2F: + l.renderRegisterU2F(w, r, authReq, nil) return } - l.renderError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-Or3HO", "Errors.User.Mfa.NoProviders")) + l.renderError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-Or3HO", "Errors.User.MFA.NoProviders")) } -func (l *Login) handleOtpCreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { - otp, err := l.authRepo.AddMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) +func (l *Login) handleOTPCreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { + otp, err := l.authRepo.AddMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) if err != nil { l.renderError(w, r, authReq, err) return @@ -96,5 +99,5 @@ func (l *Login) handleOtpCreation(w http.ResponseWriter, r *http.Request, authRe Secret: otp.SecretString, Url: otp.Url, } - l.renderMfaInitVerify(w, r, authReq, data, nil) + l.renderMFAInitVerify(w, r, authReq, data, nil) } diff --git a/internal/ui/login/handler/mfa_verify_handler.go b/internal/ui/login/handler/mfa_verify_handler.go index 3c22f854ae..d2819afd1c 100644 --- a/internal/ui/login/handler/mfa_verify_handler.go +++ b/internal/ui/login/handler/mfa_verify_handler.go @@ -8,24 +8,24 @@ import ( ) const ( - tmplMfaVerify = "mfaverify" + tmplMFAVerify = "mfaverify" ) type mfaVerifyFormData struct { - MfaType model.MFAType `schema:"mfaType"` + MFAType model.MFAType `schema:"mfaType"` Code string `schema:"code"` } -func (l *Login) handleMfaVerify(w http.ResponseWriter, r *http.Request) { +func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) { data := new(mfaVerifyFormData) authReq, err := l.getAuthRequestAndParseData(r, data) if err != nil { l.renderError(w, r, authReq, err) return } - if data.MfaType == model.MFATypeOTP { + if data.MFAType == model.MFATypeOTP { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.VerifyMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, userAgentID, model.BrowserInfoFromRequest(r)) + err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, userAgentID, model.BrowserInfoFromRequest(r)) } if err != nil { l.renderError(w, r, authReq, err) @@ -34,15 +34,23 @@ func (l *Login) handleMfaVerify(w http.ResponseWriter, r *http.Request) { l.renderNextStep(w, r, authReq) } -func (l *Login) renderMfaVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MfaVerificationStep, err error) { +func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MFAVerificationStep, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) } - data := l.getUserData(r, authReq, "Mfa Verify", errType, errMessage) - if verificationStep != nil { - data.MfaProviders = verificationStep.MfaProviders - data.SelectedMfaProvider = verificationStep.MfaProviders[0] + data := l.getUserData(r, authReq, "MFA Verify", errType, errMessage) + if verificationStep == nil { + l.renderError(w, r, authReq, err) + return } - l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaVerify], data, nil) + switch verificationStep.MFAProviders[len(verificationStep.MFAProviders)-1] { + case model.MFATypeU2F: + l.renderU2FVerification(w, r, authReq, nil) + return + case model.MFATypeOTP: + data.MFAProviders = verificationStep.MFAProviders + data.SelectedMFAProvider = model.MFATypeOTP + } + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAVerify], data, nil) } diff --git a/internal/ui/login/handler/mfa_verify_u2f_handler.go b/internal/ui/login/handler/mfa_verify_u2f_handler.go new file mode 100644 index 0000000000..51b734ab0a --- /dev/null +++ b/internal/ui/login/handler/mfa_verify_u2f_handler.go @@ -0,0 +1,59 @@ +package handler + +import ( + "encoding/base64" + "net/http" + + http_mw "github.com/caos/zitadel/internal/api/http/middleware" + "github.com/caos/zitadel/internal/auth_request/model" + user_model "github.com/caos/zitadel/internal/user/model" +) + +const ( + tmplU2FVerification = "u2fverification" +) + +func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { + var errType, errMessage, credentialData string + var webAuthNLogin *user_model.WebAuthNLogin + if err == nil { + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + webAuthNLogin, err = l.authRepo.BeginMFAU2FLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID) + } + if err != nil { + errMessage = l.getErrorMessage(r, err) + } + if webAuthNLogin != nil { + credentialData = base64.RawURLEncoding.EncodeToString(webAuthNLogin.CredentialAssertionData) + } + data := &webAuthNData{ + userData: l.getUserData(r, authReq, "Login WebAuthNToken", errType, errMessage), + CredentialCreationData: credentialData, + } + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplU2FVerification], data, nil) +} + +func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) { + formData := new(webAuthNFormData) + authReq, err := l.getAuthRequestAndParseData(r, formData) + if err != nil { + l.renderError(w, r, authReq, err) + return + } + if formData.Recreate { + l.renderU2FVerification(w, r, authReq, nil) + return + } + credData, err := base64.URLEncoding.DecodeString(formData.CredentialData) + if err != nil { + l.renderU2FVerification(w, r, authReq, err) + return + } + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID, credData, model.BrowserInfoFromRequest(r)) + if err != nil { + l.renderU2FVerification(w, r, authReq, err) + return + } + l.renderNextStep(w, r, authReq) +} diff --git a/internal/ui/login/handler/passwordless_login_handler.go b/internal/ui/login/handler/passwordless_login_handler.go new file mode 100644 index 0000000000..116f0b9cbf --- /dev/null +++ b/internal/ui/login/handler/passwordless_login_handler.go @@ -0,0 +1,59 @@ +package handler + +import ( + "encoding/base64" + "net/http" + + http_mw "github.com/caos/zitadel/internal/api/http/middleware" + "github.com/caos/zitadel/internal/auth_request/model" + user_model "github.com/caos/zitadel/internal/user/model" +) + +const ( + tmplPasswordlessVerification = "passwordlessverification" +) + +func (l *Login) renderPasswordlessVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { + var errType, errMessage, credentialData string + var webAuthNLogin *user_model.WebAuthNLogin + if err == nil { + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + webAuthNLogin, err = l.authRepo.BeginPasswordlessLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID) + } + if err != nil { + errMessage = l.getErrorMessage(r, err) + } + if webAuthNLogin != nil { + credentialData = base64.RawURLEncoding.EncodeToString(webAuthNLogin.CredentialAssertionData) + } + data := &webAuthNData{ + userData: l.getUserData(r, authReq, "Login Passwordless", errType, errMessage), + CredentialCreationData: credentialData, + } + l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplPasswordlessVerification], data, nil) +} + +func (l *Login) handlePasswordlessVerification(w http.ResponseWriter, r *http.Request) { + formData := new(webAuthNFormData) + authReq, err := l.getAuthRequestAndParseData(r, formData) + if err != nil { + l.renderError(w, r, authReq, err) + return + } + if formData.Recreate { + l.renderPasswordlessVerification(w, r, authReq, nil) + return + } + credData, err := base64.URLEncoding.DecodeString(formData.CredentialData) + if err != nil { + l.renderPasswordlessVerification(w, r, authReq, err) + return + } + userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) + err = l.authRepo.VerifyPasswordless(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID, credData, model.BrowserInfoFromRequest(r)) + if err != nil { + l.renderPasswordlessVerification(w, r, authReq, err) + return + } + l.renderNextStep(w, r, authReq) +} diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go index 7b1d563551..dad96049cb 100644 --- a/internal/ui/login/handler/renderer.go +++ b/internal/ui/login/handler/renderer.go @@ -3,18 +3,19 @@ package handler import ( "errors" "fmt" - "github.com/caos/logging" - iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/gorilla/csrf" - "golang.org/x/text/language" "html/template" "net/http" "path" + "github.com/caos/logging" + "github.com/gorilla/csrf" + "golang.org/x/text/language" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/i18n" + iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/renderer" ) @@ -32,31 +33,34 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str pathPrefix: pathPrefix, } tmplMapping := map[string]string{ - tmplError: "error.html", - tmplLogin: "login.html", - tmplUserSelection: "select_user.html", - tmplPassword: "password.html", - tmplMfaVerify: "mfa_verify.html", - tmplMfaPrompt: "mfa_prompt.html", - tmplMfaInitVerify: "mfa_init_verify.html", - tmplMfaInitDone: "mfa_init_done.html", - tmplMailVerification: "mail_verification.html", - tmplMailVerified: "mail_verified.html", - tmplInitPassword: "init_password.html", - tmplInitPasswordDone: "init_password_done.html", - tmplInitUser: "init_user.html", - tmplInitUserDone: "init_user_done.html", - tmplPasswordResetDone: "password_reset_done.html", - tmplChangePassword: "change_password.html", - tmplChangePasswordDone: "change_password_done.html", - tmplRegisterOption: "register_option.html", - tmplRegister: "register.html", - tmplLogoutDone: "logout_done.html", - tmplRegisterOrg: "register_org.html", - tmplChangeUsername: "change_username.html", - tmplChangeUsernameDone: "change_username_done.html", - tmplLinkUsersDone: "link_users_done.html", - tmplExternalNotFoundOption: "external_not_found_option.html", + tmplError: "error.html", + tmplLogin: "login.html", + tmplUserSelection: "select_user.html", + tmplPassword: "password.html", + tmplPasswordlessVerification: "passwordless.html", + tmplMFAVerify: "mfa_verify.html", + tmplMFAPrompt: "mfa_prompt.html", + tmplMFAInitVerify: "mfa_init_verify.html", + tmplMFAU2FInit: "mfa_init_u2f.html", + tmplU2FVerification: "mfa_verification_u2f.html", + tmplMFAInitDone: "mfa_init_done.html", + tmplMailVerification: "mail_verification.html", + tmplMailVerified: "mail_verified.html", + tmplInitPassword: "init_password.html", + tmplInitPasswordDone: "init_password_done.html", + tmplInitUser: "init_user.html", + tmplInitUserDone: "init_user_done.html", + tmplPasswordResetDone: "password_reset_done.html", + tmplChangePassword: "change_password.html", + tmplChangePasswordDone: "change_password_done.html", + tmplRegisterOption: "register_option.html", + tmplRegister: "register.html", + tmplLogoutDone: "logout_done.html", + tmplRegisterOrg: "register_org.html", + tmplChangeUsername: "change_username.html", + tmplChangeUsernameDone: "change_username_done.html", + tmplLinkUsersDone: "link_users_done.html", + tmplExternalNotFoundOption: "external_not_found_option.html", } funcs := map[string]interface{}{ "resourceUrl": func(file string) string { @@ -86,6 +90,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str "userSelectionUrl": func() string { return path.Join(r.pathPrefix, EndpointUserSelection) }, + "passwordLessVerificationUrl": func() string { + return path.Join(r.pathPrefix, EndpointPasswordlessLogin) + }, "passwordResetUrl": func(id string) string { return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointPasswordReset, queryAuthRequestID, id)) }, @@ -93,16 +100,22 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str return path.Join(r.pathPrefix, EndpointPassword) }, "mfaVerifyUrl": func() string { - return path.Join(r.pathPrefix, EndpointMfaVerify) + return path.Join(r.pathPrefix, EndpointMFAVerify) }, "mfaPromptUrl": func() string { - return path.Join(r.pathPrefix, EndpointMfaPrompt) + return path.Join(r.pathPrefix, EndpointMFAPrompt) }, "mfaPromptChangeUrl": func(id string, provider model.MFAType) string { - return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s;%s=%v", EndpointMfaPrompt, queryAuthRequestID, id, "provider", provider)) + return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s;%s=%v", EndpointMFAPrompt, queryAuthRequestID, id, "provider", provider)) }, "mfaInitVerifyUrl": func() string { - return path.Join(r.pathPrefix, EndpointMfaInitVerify) + return path.Join(r.pathPrefix, EndpointMFAInitVerify) + }, + "mfaInitU2FVerifyUrl": func() string { + return path.Join(r.pathPrefix, EndpointMFAInitU2FVerify) + }, + "mfaInitU2FLoginUrl": func() string { + return path.Join(r.pathPrefix, EndpointU2FVerification) }, "mailVerificationUrl": func() string { return path.Join(r.pathPrefix, EndpointMailVerification) @@ -190,8 +203,10 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) case *model.PasswordStep: l.renderPassword(w, r, authReq, nil) - case *model.MfaVerificationStep: - l.renderMfaVerify(w, r, authReq, step, err) + case *model.PasswordlessStep: + l.renderPasswordlessVerification(w, r, authReq, nil) + case *model.MFAVerificationStep: + l.renderMFAVerify(w, r, authReq, step, err) case *model.RedirectToCallbackStep: if len(authReq.PossibleSteps) > 1 { l.chooseNextStep(w, r, authReq, 1, err) @@ -202,8 +217,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * l.renderChangePassword(w, r, authReq, err) case *model.VerifyEMailStep: l.renderMailVerification(w, r, authReq, "", err) - case *model.MfaPromptStep: - l.renderMfaPrompt(w, r, authReq, step, err) + case *model.MFAPromptStep: + l.renderMFAPrompt(w, r, authReq, step, err) case *model.InitUserStep: l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil) case *model.ChangeUsernameStep: @@ -356,8 +371,8 @@ type userData struct { baseData profileData PasswordChecked string - MfaProviders []model.MFAType - SelectedMfaProvider model.MFAType + MFAProviders []model.MFAType + SelectedMFAProvider model.MFAType Linking bool } @@ -386,21 +401,21 @@ type userSelectionData struct { type mfaData struct { baseData profileData - MfaProviders []model.MFAType - MfaRequired bool + MFAProviders []model.MFAType + MFARequired bool } type mfaVerifyData struct { baseData profileData - MfaType model.MFAType + MFAType model.MFAType otpData } type mfaDoneData struct { baseData profileData - MfaType model.MFAType + MFAType model.MFAType } type otpData struct { diff --git a/internal/ui/login/handler/router.go b/internal/ui/login/handler/router.go index 7b1651b9eb..6d3b8cde36 100644 --- a/internal/ui/login/handler/router.go +++ b/internal/ui/login/handler/router.go @@ -13,6 +13,7 @@ const ( EndpointLogin = "/login" EndpointExternalLogin = "/login/externalidp" EndpointExternalLoginCallback = "/login/externalidp/callback" + EndpointPasswordlessLogin = "/login/passwordless" EndpointLoginName = "/loginname" EndpointUserSelection = "/userselection" EndpointChangeUsername = "/username/change" @@ -21,9 +22,11 @@ const ( EndpointChangePassword = "/password/change" EndpointPasswordReset = "/password/reset" EndpointInitUser = "/user/init" - EndpointMfaVerify = "/mfa/verify" - EndpointMfaPrompt = "/mfa/prompt" - EndpointMfaInitVerify = "/mfa/init/verify" + EndpointMFAVerify = "/mfa/verify" + EndpointMFAPrompt = "/mfa/prompt" + EndpointMFAInitVerify = "/mfa/init/verify" + EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify" + EndpointU2FVerification = "/mfa/u2f/verify" EndpointMailVerification = "/mail/verification" EndpointMailVerified = "/mail/verified" EndpointRegisterOption = "/register/option" @@ -46,6 +49,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost) router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet) router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet) + router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost) router.HandleFunc(EndpointLoginName, login.handleLoginName).Methods(http.MethodGet) router.HandleFunc(EndpointLoginName, login.handleLoginNameCheck).Methods(http.MethodPost) router.HandleFunc(EndpointUserSelection, login.handleSelectUser).Methods(http.MethodPost) @@ -56,10 +60,12 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M router.HandleFunc(EndpointPasswordReset, login.handlePasswordReset).Methods(http.MethodGet) router.HandleFunc(EndpointInitUser, login.handleInitUser).Methods(http.MethodGet) router.HandleFunc(EndpointInitUser, login.handleInitUserCheck).Methods(http.MethodPost) - router.HandleFunc(EndpointMfaVerify, login.handleMfaVerify).Methods(http.MethodPost) - router.HandleFunc(EndpointMfaPrompt, login.handleMfaPromptSelection).Methods(http.MethodGet) - router.HandleFunc(EndpointMfaPrompt, login.handleMfaPrompt).Methods(http.MethodPost) - router.HandleFunc(EndpointMfaInitVerify, login.handleMfaInitVerify).Methods(http.MethodPost) + router.HandleFunc(EndpointMFAVerify, login.handleMFAVerify).Methods(http.MethodPost) + router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet) + router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost) + router.HandleFunc(EndpointMFAInitVerify, login.handleMFAInitVerify).Methods(http.MethodPost) + router.HandleFunc(EndpointMFAInitU2FVerify, login.handleRegisterU2F).Methods(http.MethodPost) + router.HandleFunc(EndpointU2FVerification, login.handleU2FVerification).Methods(http.MethodPost) router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet) router.HandleFunc(EndpointMailVerification, login.handleMailVerificationCheck).Methods(http.MethodPost) router.HandleFunc(EndpointChangePassword, login.handleChangePassword).Methods(http.MethodPost) diff --git a/internal/ui/login/handler/webauthn.go b/internal/ui/login/handler/webauthn.go new file mode 100644 index 0000000000..83ade70060 --- /dev/null +++ b/internal/ui/login/handler/webauthn.go @@ -0,0 +1,12 @@ +package handler + +type webAuthNData struct { + userData + CredentialCreationData string +} + +type webAuthNFormData struct { + CredentialData string `schema:"credentialData"` + Name string `schema:"name"` + Recreate bool `schema:"recreate"` +} diff --git a/internal/ui/login/static/i18n/de.yaml b/internal/ui/login/static/i18n/de.yaml index 0954839834..165d5eb443 100644 --- a/internal/ui/login/static/i18n/de.yaml +++ b/internal/ui/login/static/i18n/de.yaml @@ -35,10 +35,10 @@ UsernameChangeDone: Title: Username geändert Description: Der Username wurde erfolgreich geändert. -MfaVerify: +MFAVerify: Title: Multifaktor verifizieren Description: Verifiziere deinen Multifaktor - OTP: OTP + OTP: OTP (One Time Password) Code: Code InitPassword: @@ -63,23 +63,41 @@ InitUserDone: Title: User aktiviert Description: EMail verifiziert und Passwort erfolgreich gesetzt -MfaPrompt: +MFAPrompt: Title: Multifaktor hinzufügen Description: Möchtest du einen Mulitfaktor hinzufügen? Provider0: OTP (One Time Password) Provider1: U2F (Universal 2nd Factor) -MfaInitVerify: +MFAInitVerify: Title: Multifaktor Verifizierung Description: Verifiziere deinen Multifaktor - OtpDescription: Scanne den Code mit einem Authentifizierungs-App (z.B Google Authentificator) oder kopiere das Secret und gib anschliessend den Code ein. + OTPDescription: Scanne den Code mit einem Authentifizierungs-App (z.B Google Authenticator) oder kopiere das Secret und gib anschliessend den Code ein. Secret: Secret Code: Code -MfaInitDone: +MFAInitDone: Title: Multifaktor Verifizierung erstellt Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden, dies beinhaltet auch den aktuellen Authentifizierungs Prozess. +MFAInitU2F: + Title: Multifaktor U2F / WebAuthN hinzufügen + Description: Füge dein Token hinzu, indem du einen Namen eingibst und den 'Token registrieren' Button drückst. + +MFAVerifyU2F: + Title: Multifaktor Verifizierung + Description: Verifiziere deinen Multifaktor U2F / WebAuthN Token + +WebAuthN: + Name: Name des Tokens / Geräts + NotSupported: WebAuthN wird durch deinen Browser nicht unterstützt. Stelle sicher, dass du die aktuelle Version installiert hast oder nutze einen anderen (z.B. Chrome, Safari, Firefox) + Error: + Retry: Versuche es erneut, erstelle eine neue Abfrage oder wähle einen andere Methode. + +Passwordless: + Title: Passwortlos einloggen + Description: Verifiziere dein Token + PasswordChange: Title: Passwort ändern Description: Ändere dein Password in dem du dein altes und dann dein neuen Passwort eingibst. @@ -181,6 +199,9 @@ Actions: ForgotPassword: Password zurücksetzen Cancel: Abbrechen Save: speichern + RegisterToken: Token registrieren + ValidateToken: Token validieren + Recreate: erneut erstellen Errors: Internal: Es ist ein interner Fehler aufgetreten @@ -215,9 +236,9 @@ Errors: GeneratorAlgNotSupported: Generator Algorithums wird nicht unterstützt EmailVerify: UserIDEmpty: UserID ist leer - Mfa: + MFA: NoProviders: Es stehen keine Multifaktorprovider zur Verfügung - Otp: + OTP: AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet NotExisting: Multifaktor OTP (OneTimePassword) existiert nicht InvalidCode: Code ist ungültig diff --git a/internal/ui/login/static/i18n/en.yaml b/internal/ui/login/static/i18n/en.yaml index ffaff11d73..f1c0d1f555 100644 --- a/internal/ui/login/static/i18n/en.yaml +++ b/internal/ui/login/static/i18n/en.yaml @@ -35,10 +35,10 @@ UsernameChangeDone: Title: Username changed Description: Your username was changed successfully. -MfaVerify: +MFAVerify: Title: Verify Multificator Description: Verify your multifactor - OTP: OTP + OTP: OTP (One Time Password) Code: Code InitPassword: @@ -63,23 +63,41 @@ InitUserDone: Title: User activated Description: Email verified and Password successfully set -MfaPrompt: +MFAPrompt: Title: Multifactor Setup Description: Would you like to setup multifactor authentication? Provider0: OTP (One Time Password) Provider1: U2F (Universal 2nd Factor) -MfaInitVerify: +MFAInitVerify: Title: Multifactor Verification Description: Verify your multifactor. - OtpDescription: Scan the code with your authenticator app (e.g Google-Authenticator) or copy the secret and insert the generated code below. + OTPDescription: Scan the code with your authenticator app (e.g Google Authenticator) or copy the secret and insert the generated code below. Secret: Secret Code: Code -MfaInitDone: +MFAInitDone: Title: Multifcator Verification done Description: Multifactor verification successfully done. The multifactor has to be entered on each login, even in the actual authentification process. +MFAInitU2F: + Title: Multifactor Setup U2F / WebAuthN + Description: Add your Token by providing a name and then clicking on the 'Register Token' button below. + +MFAVerifyU2F: + Title: Multifactor Verification + Description: Verify your multifactor U2F / WebAuthN token + +WebAuthN: + Name: Name of the tokens / machine + NotSupported: WebAuthN is not supported by your browser. Please ensure it is up to date or use a different one (e.g. Chrome, Safari, Firefox) + Error: + Retry: Retry, create a new challenge or choose a different method. + +Passwordless: + Title: Login passwordles + Description: Verify your token + PasswordChange: Title: Change Password Description: Change your password. Enter your old and new password. @@ -181,6 +199,9 @@ Actions: ForgotPassword: reset password Cancel: cancel Save: save + RegisterToken: Register Token + ValidateToken: Validate Token + Recreate: recreate Errors: Internal: An internal error occured @@ -215,9 +236,9 @@ Errors: GeneratorAlgNotSupported: Unsupported generator algorithm EmailVerify: UserIDEmpty: UserID is empty - Mfa: + MFA: NoProviders: No available multifactor providers - Otp: + OTP: AlreadyReady: Multifactor OTP (OneTimePassword) is already setup NotExisting: Multifactor OTP (OneTimePassword) doesn't exist InvalidCode: Invalid code diff --git a/internal/ui/login/static/resources/scripts/base64.js b/internal/ui/login/static/resources/scripts/base64.js new file mode 100644 index 0000000000..075fe9d48d --- /dev/null +++ b/internal/ui/login/static/resources/scripts/base64.js @@ -0,0 +1,68 @@ +/* + * modified version of: + * + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ + +"use strict"; + +let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Use a lookup table to find the index. +let lookup = new Uint8Array(256); +for (var i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; +} + +function encode(arraybuffer) { + let bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64 = ""; + + for (i = 0; i < len; i += 3) { + base64 += chars[bytes[i] >> 2]; + base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + "="; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + "=="; + } + + return base64; +} + +function decode(base64) { + let bufferLength = base64.length * 0.75, + len = base64.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + let arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i += 4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i + 1)]; + encoded3 = lookup[base64.charCodeAt(i + 2)]; + encoded4 = lookup[base64.charCodeAt(i + 3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; +} \ No newline at end of file diff --git a/internal/ui/login/static/resources/scripts/webauthn.js b/internal/ui/login/static/resources/scripts/webauthn.js new file mode 100644 index 0000000000..74c54c4db5 --- /dev/null +++ b/internal/ui/login/static/resources/scripts/webauthn.js @@ -0,0 +1,31 @@ +function checkWebauthnSupported(button, func) { + let support = document.getElementsByClassName("wa-support"); + let noSupport = document.getElementsByClassName("wa-no-support"); + if (typeof (PublicKeyCredential) === undefined) { + for (let item of noSupport) { + item.classList.remove('hidden'); + } + for (let item of support) { + item.classList.add('hidden'); + } + return + } + document.getElementById(button).addEventListener('click', func); +} + +function webauthnError(error) { + let err = document.getElementById('wa-error'); + err.getElementsByClassName('cause')[0].innerText = error.message; + err.classList.remove('hidden'); +} + +function bufferDecode(value) { + return decode(value); +} + +function bufferEncode(value) { + return encode(value) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); +} diff --git a/internal/ui/login/static/resources/scripts/webauthn_login.js b/internal/ui/login/static/resources/scripts/webauthn_login.js new file mode 100644 index 0000000000..a9264ffe1f --- /dev/null +++ b/internal/ui/login/static/resources/scripts/webauthn_login.js @@ -0,0 +1,42 @@ +document.addEventListener('DOMContentLoaded', checkWebauthnSupported('btn-login', login)); + +function login() { + document.getElementById('wa-error').classList.add('hidden'); + + let makeAssertionOptions = JSON.parse(atob(document.getElementsByName('credentialAssertionData')[0].value)); + makeAssertionOptions.publicKey.challenge = bufferDecode(makeAssertionOptions.publicKey.challenge); + makeAssertionOptions.publicKey.allowCredentials.forEach(function (listItem) { + listItem.id = bufferDecode(listItem.id) + }); + console.log(makeAssertionOptions); + navigator.credentials.get({ + publicKey: makeAssertionOptions.publicKey + }).then(function (credential) { + verifyAssertion(credential); + }).catch(function (err) { + webauthnError(err); + }); +} + +function verifyAssertion(assertedCredential) { + let authData = new Uint8Array(assertedCredential.response.authenticatorData); + let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON); + let rawId = new Uint8Array(assertedCredential.rawId); + let sig = new Uint8Array(assertedCredential.response.signature); + let userHandle = new Uint8Array(assertedCredential.response.userHandle); + + let data = JSON.stringify({ + id: assertedCredential.id, + rawId: bufferEncode(rawId), + type: assertedCredential.type, + response: { + authenticatorData: bufferEncode(authData), + clientDataJSON: bufferEncode(clientDataJSON), + signature: bufferEncode(sig), + userHandle: bufferEncode(userHandle), + }, + }) + + document.getElementsByName('credentialData')[0].value = btoa(data); + document.getElementsByTagName('form')[0].submit(); +} \ No newline at end of file diff --git a/internal/ui/login/static/resources/scripts/webauthn_register.js b/internal/ui/login/static/resources/scripts/webauthn_register.js new file mode 100644 index 0000000000..153f6f1abf --- /dev/null +++ b/internal/ui/login/static/resources/scripts/webauthn_register.js @@ -0,0 +1,42 @@ +document.addEventListener('DOMContentLoaded', checkWebauthnSupported('btn-register', registerCredential)); + +function registerCredential() { + document.getElementById('wa-error').classList.add('hidden'); + + let opt = JSON.parse(atob(document.getElementsByName('credentialCreationData')[0].value)); + opt.publicKey.challenge = bufferDecode(opt.publicKey.challenge); + opt.publicKey.user.id = bufferDecode(opt.publicKey.user.id); + if (opt.publicKey.excludeCredentials) { + for (let i = 0; i < opt.publicKey.excludeCredentials.length; i++) { + if (opt.publicKey.excludeCredentials[i].id !== null) { + opt.publicKey.excludeCredentials[i].id = bufferDecode(opt.publicKey.excludeCredentials[i].id); + } + } + } + navigator.credentials.create({ + publicKey: opt.publicKey + }).then(function (credential) { + createCredential(credential); + }).catch(function (err) { + webauthnError(err); + }); +} + +function createCredential(newCredential) { + let attestationObject = new Uint8Array(newCredential.response.attestationObject); + let clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON); + let rawId = new Uint8Array(newCredential.rawId); + + let data = JSON.stringify({ + id: newCredential.id, + rawId: bufferEncode(rawId), + type: newCredential.type, + response: { + attestationObject: bufferEncode(attestationObject), + clientDataJSON: bufferEncode(clientDataJSON), + }, + }); + + document.getElementsByName('credentialData')[0].value = btoa(data); + document.getElementsByTagName('form')[0].submit(); +} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/caos/css/dark.css b/internal/ui/login/static/resources/themes/caos/css/dark.css index 80a79a848a..d8fd885a54 100644 --- a/internal/ui/login/static/resources/themes/caos/css/dark.css +++ b/internal/ui/login/static/resources/themes/caos/css/dark.css @@ -487,4 +487,12 @@ footer { color: #F20D6B; } +.hidden { + display: none; +} + +#wa-error { + margin-top: 20px; +} + /*# sourceMappingURL=dark.css.map */ diff --git a/internal/ui/login/static/resources/themes/caos/css/dark.css.map b/internal/ui/login/static/resources/themes/caos/css/dark.css.map index 449a4e6fe6..5890330f09 100644 --- a/internal/ui/login/static/resources/themes/caos/css/dark.css.map +++ b/internal/ui/login/static/resources/themes/caos/css/dark.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO","file":"dark.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO;;;AF+aX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/caos/css/light.css b/internal/ui/login/static/resources/themes/caos/css/light.css index e9168da6da..ed2241a8fe 100644 --- a/internal/ui/login/static/resources/themes/caos/css/light.css +++ b/internal/ui/login/static/resources/themes/caos/css/light.css @@ -487,6 +487,14 @@ footer { color: #F20D6B; } +.hidden { + display: none; +} + +#wa-error { + margin-top: 20px; +} + html { background-color: white; color: #282828; diff --git a/internal/ui/login/static/resources/themes/caos/css/light.css.map b/internal/ui/login/static/resources/themes/caos/css/light.css.map index aeb7aefcf1..6eb1e80ecf 100644 --- a/internal/ui/login/static/resources/themes/caos/css/light.css.map +++ b/internal/ui/login/static/resources/themes/caos/css/light.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO;;;ACrCX;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;EACA,ODyBgB;;ACtBpB;EACI,kBFVG;EEWH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBFdI;;AEkBZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,OFhCM;;AEkCN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,OF9EU;;AEkFV;EACI,MFnFM;;AEsFV;EACI,MFtFA;;AE0FR;EAEQ;;;AAMR;EACI,OFpGU;;AEwGb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCpFY;EDqFZ,OCxFU;EDyFV;;AAGJ;EACI,kBC3FO;ED4FP,OC7FI;ED8FJ;;AACA;EACI,kBC9FQ;;ADkGhB;EACI,kBExFW;EFyFX;;AAEA;EACI,kBE5FO;EF6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OEhGa;EFiGb,kBEhGmB;;AFkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBE9HmB;EF+HnB,OC7IQ;ED8IR,QE1JU;EF2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EEzJN;;AACA;EFoJE;IEnJA;IACA;;;AF0JA;EE7JF;;AACA;EF4JE;IE3JA;IACA;;;;AFiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WEvLE;EFwLF;;AAGJ;EACI;EACA;EACA;EACA,OE1KC;;;AFgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC/NA;;ADmOR;EACI,OExNK;EFyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC9PI;ED+PJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBExPW;;AF2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEjRO;EFkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OE7SP;;AFoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EEvUV;;AACA;EFkUM;IEjUJ;IACA;;;AFyUQ;EACI;EACA;EE9Ud;;AACA;EF2UU;IE1UR;IACA;;;AFgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OErVN;;AF0VE;EACI,OE5VL;;AFiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCxYI;;AD2YR;EACI,MC7YU;;;ADkZd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OE5aO;;;AF+aX;EACI;;;AAGJ;EACI;;;AGzdJ;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;EACA,ODyBgB;;ACtBpB;EACI,kBFVG;EEWH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBFdI;;AEkBZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,OFhCM;;AEkCN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,OF9EU;;AEkFV;EACI,MFnFM;;AEsFV;EACI,MFtFA;;AE0FR;EAEQ;;;AAMR;EACI,OFpGU;;AEwGb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/scss/main.scss b/internal/ui/login/static/resources/themes/scss/main.scss index 2a43896f99..9fb1bf4511 100644 --- a/internal/ui/login/static/resources/themes/scss/main.scss +++ b/internal/ui/login/static/resources/themes/scss/main.scss @@ -466,3 +466,11 @@ footer { .error { color: $nokColor; } + +.hidden { + display: none; +} + +#wa-error { + margin-top: 20px; +} diff --git a/internal/ui/login/static/resources/themes/zitadel/css/dark.css b/internal/ui/login/static/resources/themes/zitadel/css/dark.css index 87f37afe9b..f32c2bc0e0 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/dark.css +++ b/internal/ui/login/static/resources/themes/zitadel/css/dark.css @@ -487,4 +487,12 @@ footer { color: #F20D6B; } +.hidden { + display: none; +} + +#wa-error { + margin-top: 20px; +} + /*# sourceMappingURL=dark.css.map */ diff --git a/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map b/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map index 6bbf67f0ed..3c47167879 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map +++ b/internal/ui/login/static/resources/themes/zitadel/css/dark.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO","file":"dark.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO;;;AD+aX;EACI;;;AAGJ;EACI","file":"dark.css"} \ No newline at end of file diff --git a/internal/ui/login/static/resources/themes/zitadel/css/light.css b/internal/ui/login/static/resources/themes/zitadel/css/light.css index 4890c85e1b..6d23b14ad5 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/light.css +++ b/internal/ui/login/static/resources/themes/zitadel/css/light.css @@ -487,6 +487,14 @@ footer { color: #F20D6B; } +.hidden { + display: none; +} + +#wa-error { + margin-top: 20px; +} + html { background-color: #f5f5f5; color: #282828; diff --git a/internal/ui/login/static/resources/themes/zitadel/css/light.css.map b/internal/ui/login/static/resources/themes/zitadel/css/light.css.map index 0383c7d816..28d354b79b 100644 --- a/internal/ui/login/static/resources/themes/zitadel/css/light.css.map +++ b/internal/ui/login/static/resources/themes/zitadel/css/light.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO;;;ACrCX;EACI,kBD2CmB;EC1CnB,ODsBc;ECpBV;;AAGJ;EACI;;AAGJ;EACI,ODYU;;ACTd;EACI,kBD4Be;EC3Bf,ODSO;ECRP;;AAEA;EACI,kBD0Ba;ECzBb;EACA,ODyBgB;;ACtBpB;EACI,kBDDG;ECEH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,ODvBM;;ACyBN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,ODrEU;;ACyEV;EACI,MD1EM;;AC6EV;EACI,MD1DW;;ACsEnB;EACI,OD3FU;;AC+Fb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC3EY;ED4EZ,OC/EU;EDgFV;;AAGJ;EACI,kBClFO;EDmFP,OCpFI;EDqFJ;;AACA;EACI,kBCrFQ;;ADyFhB;EACI,kBCxFW;EDyFX;;AAEA;EACI,kBC5FO;ED6FP;;AAIR;EACI;EACA;EACA;EACA;EACA,OChGa;EDiGb,kBChGmB;;ADkGnB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;;AAOZ;EACI,kBC9HmB;ED+HnB,OCpIQ;EDqIR,QC1JU;ED2JV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;ECzJN;;AACA;EDoJE;ICnJA;IACA;;;AD0JA;EC7JF;;AACA;ED4JE;IC3JA;IACA;;;;ADiKA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WCvLE;EDwLF;;AAGJ;EACI;EACA;EACA;EACA,OC1KC;;;ADgLT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCtNA;;AD0NR;EACI,OCxNK;EDyNL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCrPI;EDsPJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBCxPW;;AD2Pf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCjRO;EDkRP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OC7SP;;ADoTL;EACI;;AAEJ;EACI;EACA;EACA;EACA;ECvUV;;AACA;EDkUM;ICjUJ;IACA;;;ADyUQ;EACI;EACA;EC9Ud;;AACA;ED2UU;IC1UR;IACA;;;ADgVI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OCrVN;;AD0VE;EACI,OC5VL;;ADiWP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC/XI;;ADkYR;EACI,MCpYU;;;ADyYd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OC5aO;;;AD+aX;EACI;;;AAGJ;EACI;;;AEzdJ;EACI,kBD2CmB;EC1CnB,ODsBc;ECpBV;;AAGJ;EACI;;AAGJ;EACI,ODYU;;ACTd;EACI,kBD4Be;EC3Bf,ODSO;ECRP;;AAEA;EACI,kBD0Ba;ECzBb;EACA,ODyBgB;;ACtBpB;EACI,kBDDG;ECEH,ODoBgB;ECnBhB;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,ODvBM;;ACyBN;EACI;EACA,kBDHY;;ACQhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDbY;;ACeZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC8DI;EACI,OD7Bc;EC8Bd,kBD7BoB;;AC+BpB;EACI;;AAKZ;EACI,kBD5CoB;EC6CpB,ODrEU;;ACyEV;EACI,MD1EM;;AC6EV;EACI,MD1DW;;ACsEnB;EACI,OD3FU;;AC+Fb;EACI,ODhEM;;ACoEN;EACI,ODtEG;;;AC8EZ;EDrHF;;AACA;ECoHE;IDnHA;IACA;;;ACsHA;EDzHF;;AACA;ECwHE;IDvHA;IACA;;;;AC2HJ;EACI;;;AAGJ;EACI,OD5FY","file":"light.css"} \ No newline at end of file diff --git a/internal/ui/login/static/templates/mfa_init_done.html b/internal/ui/login/static/templates/mfa_init_done.html index 0a233c35ba..7b171e6848 100644 --- a/internal/ui/login/static/templates/mfa_init_done.html +++ b/internal/ui/login/static/templates/mfa_init_done.html @@ -3,7 +3,7 @@
{{ template "user-profile" . }} -

{{t "MfaInitDone.Description"}}

+

{{t "MFAInitDone.Description"}}

@@ -11,7 +11,7 @@ {{ .CSRF }} - +
diff --git a/internal/ui/login/static/templates/mfa_init_u2f.html b/internal/ui/login/static/templates/mfa_init_u2f.html new file mode 100644 index 0000000000..f9f7de78c6 --- /dev/null +++ b/internal/ui/login/static/templates/mfa_init_u2f.html @@ -0,0 +1,42 @@ +{{template "main-top" .}} + +
+ {{ template "user-profile" . }} + +

{{t "MFAInitU2F.Description"}}

+
+ + + + {{ .CSRF }} + + + + + +
+ +
+ + +
+ + +
+ + {{ template "error-message" .}} + +
+ +
+ + + + + + +{{template "main-bottom" .}} + \ No newline at end of file diff --git a/internal/ui/login/static/templates/mfa_init_verify.html b/internal/ui/login/static/templates/mfa_init_verify.html index ab70e39fe6..f4f8839737 100644 --- a/internal/ui/login/static/templates/mfa_init_verify.html +++ b/internal/ui/login/static/templates/mfa_init_verify.html @@ -3,7 +3,7 @@
{{ template "user-profile" . }} -

{{t "MfaInitVerify.Description"}}

+

{{t "MFAInitVerify.Description"}}

@@ -11,25 +11,25 @@ {{ .CSRF }} - + - {{if (eq .MfaType 0) }} -

{{t "MfaInitVerify.OtpDescription"}}

+ {{if (eq .MFAType 0) }} +

{{t "MFAInitVerify.OTPDescription"}}

{{.QrCode}}
- {{t "MfaInitVerify.Secret"}} + {{t "MFAInitVerify.Secret"}} {{.Secret}} content_copy
- +
@@ -37,7 +37,7 @@
- + {{t "Actions.Back"}} diff --git a/internal/ui/login/static/templates/mfa_prompt.html b/internal/ui/login/static/templates/mfa_prompt.html index cf9dac3b68..d5ae8efab0 100644 --- a/internal/ui/login/static/templates/mfa_prompt.html +++ b/internal/ui/login/static/templates/mfa_prompt.html @@ -3,7 +3,7 @@
{{ template "user-profile" . }} -

{{t "MfaPrompt.Description"}}

+

{{t "MFAPrompt.Description"}}

@@ -13,8 +13,8 @@
- {{ range $provider := .MfaProviders}} - {{ $providerName := (t (printf "MfaPrompt.Provider%v" $provider)) }} + {{ range $provider := .MFAProviders}} + {{ $providerName := (t (printf "MFAPrompt.Provider%v" $provider)) }}
@@ -24,7 +24,7 @@
- {{if not .MfaRequired}} + {{if not .MFARequired}} {{end}} diff --git a/internal/ui/login/static/templates/mfa_verification_u2f.html b/internal/ui/login/static/templates/mfa_verification_u2f.html new file mode 100644 index 0000000000..1e44ef72dc --- /dev/null +++ b/internal/ui/login/static/templates/mfa_verification_u2f.html @@ -0,0 +1,37 @@ +{{template "main-top" .}} + +
+ {{ template "user-profile" . }} + +

{{t "MFAVerifyU2F.Description"}}

+
+ + + + {{ .CSRF }} + + + + + +
+ + {{ template "error-message" .}} + +
+ +
+ + + + + + +{{template "main-bottom" .}} diff --git a/internal/ui/login/static/templates/mfa_verify.html b/internal/ui/login/static/templates/mfa_verify.html index eae77ba47e..8addc37495 100644 --- a/internal/ui/login/static/templates/mfa_verify.html +++ b/internal/ui/login/static/templates/mfa_verify.html @@ -3,7 +3,7 @@
{{ template "user-profile" . }} -

{{t "MfaVerify.Description"}}

+

{{t "MFAVerify.Description"}}

@@ -11,11 +11,11 @@ {{ .CSRF }} - +
- +
diff --git a/internal/ui/login/static/templates/passwordless.html b/internal/ui/login/static/templates/passwordless.html new file mode 100644 index 0000000000..a773234a8d --- /dev/null +++ b/internal/ui/login/static/templates/passwordless.html @@ -0,0 +1,37 @@ +{{template "main-top" .}} + +
+ {{ template "user-profile" . }} + +

{{t "Passwordless.Description"}}

+
+ + + + {{ .CSRF }} + + + + + +
+ + + +
+ + {{ template "error-message" .}} + +
+ +
+
+ + + + + +{{template "main-bottom" .}} diff --git a/internal/user/model/mfa.go b/internal/user/model/otp.go similarity index 55% rename from internal/user/model/mfa.go rename to internal/user/model/otp.go index 6a188c144e..3317ae6b99 100644 --- a/internal/user/model/mfa.go +++ b/internal/user/model/otp.go @@ -11,26 +11,27 @@ type OTP struct { Secret *crypto.CryptoValue SecretString string Url string - State MfaState + State MFAState } -type MfaState int32 +type MFAState int32 const ( - MfaStateUnspecified MfaState = iota - MfaStateNotReady - MfaStateReady + MFAStateUnspecified MFAState = iota + MFAStateNotReady + MFAStateReady ) type MultiFactor struct { - Type MfaType - State MfaState + Type MFAType + State MFAState + Attribute string } -type MfaType int32 +type MFAType int32 const ( - MfaTypeUnspecified MfaType = iota - MfaTypeOTP - MfaTypeSMS + MFATypeUnspecified MFAType = iota + MFATypeOTP + MFATypeU2F ) diff --git a/internal/user/model/user_human.go b/internal/user/model/user_human.go index 91a988b53c..c1af161fd5 100644 --- a/internal/user/model/user_human.go +++ b/internal/user/model/user_human.go @@ -1,9 +1,11 @@ package model import ( - iam_model "github.com/caos/zitadel/internal/iam/model" + "bytes" "time" + iam_model "github.com/caos/zitadel/internal/iam/model" + "github.com/caos/zitadel/internal/crypto" es_models "github.com/caos/zitadel/internal/eventstore/models" ) @@ -14,12 +16,16 @@ type Human struct { *Email *Phone *Address - ExternalIDPs []*ExternalIDP - InitCode *InitUserCode - EmailCode *EmailCode - PhoneCode *PhoneCode - PasswordCode *PasswordCode - OTP *OTP + ExternalIDPs []*ExternalIDP + InitCode *InitUserCode + EmailCode *EmailCode + PhoneCode *PhoneCode + PasswordCode *PasswordCode + OTP *OTP + U2FTokens []*WebAuthNToken + PasswordlessTokens []*WebAuthNToken + U2FLogins []*WebAuthNLogin + PasswordlessLogins []*WebAuthNLogin } type InitUserCode struct { @@ -53,7 +59,7 @@ func (u *Human) IsInitialState() bool { } func (u *Human) IsOTPReady() bool { - return u.OTP != nil && u.OTP.State == MfaStateReady + return u.OTP != nil && u.OTP.State == MFAStateReady } func (u *Human) HashPasswordIfExisting(policy *iam_model.PasswordComplexityPolicyView, passwordAlg crypto.HashAlgorithm, onetime bool) error { @@ -105,3 +111,75 @@ func (u *Human) GetExternalIDP(externalIDP *ExternalIDP) (int, *ExternalIDP) { } return -1, nil } + +func (u *Human) GetU2F(webAuthNTokenID string) (int, *WebAuthNToken) { + for i, u2f := range u.U2FTokens { + if u2f.WebAuthNTokenID == webAuthNTokenID { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetU2FByKeyID(keyID []byte) (int, *WebAuthNToken) { + for i, u2f := range u.U2FTokens { + if bytes.Compare(u2f.KeyID, keyID) == 0 { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetU2FToVerify() (int, *WebAuthNToken) { + for i, u2f := range u.U2FTokens { + if u2f.State == MFAStateNotReady { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetPasswordless(webAuthNTokenID string) (int, *WebAuthNToken) { + for i, u2f := range u.PasswordlessTokens { + if u2f.WebAuthNTokenID == webAuthNTokenID { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetPasswordlessByKeyID(keyID []byte) (int, *WebAuthNToken) { + for i, pwl := range u.PasswordlessTokens { + if bytes.Compare(pwl.KeyID, keyID) == 0 { + return i, pwl + } + } + return -1, nil +} + +func (u *Human) GetPasswordlessToVerify() (int, *WebAuthNToken) { + for i, u2f := range u.PasswordlessTokens { + if u2f.State == MFAStateNotReady { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetU2FLogin(authReqID string) (int, *WebAuthNLogin) { + for i, u2f := range u.U2FLogins { + if u2f.AuthRequest.ID == authReqID { + return i, u2f + } + } + return -1, nil +} + +func (u *Human) GetPasswordlessLogin(authReqID string) (int, *WebAuthNLogin) { + for i, pw := range u.PasswordlessLogins { + if pw.AuthRequest.ID == authReqID { + return i, pw + } + } + return -1, nil +} diff --git a/internal/user/model/user_session_view.go b/internal/user/model/user_session_view.go index fd1a43c51b..9860c7d9ed 100644 --- a/internal/user/model/user_session_view.go +++ b/internal/user/model/user_session_view.go @@ -19,6 +19,7 @@ type UserSessionView struct { DisplayName string SelectedIDPConfigID string PasswordVerification time.Time + PasswordlessVerification time.Time ExternalLoginVerification time.Time SecondFactorVerification time.Time SecondFactorVerificationType req_model.MFAType diff --git a/internal/user/model/user_view.go b/internal/user/model/user_view.go index 96826f0e4e..019c92bbe3 100644 --- a/internal/user/model/user_view.go +++ b/internal/user/model/user_view.go @@ -1,14 +1,15 @@ package model import ( - iam_model "github.com/caos/zitadel/internal/iam/model" "time" + "golang.org/x/text/language" + req_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" + iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/model" - "golang.org/x/text/language" ) type UserView struct { @@ -46,12 +47,20 @@ type HumanView struct { PostalCode string Region string StreetAddress string - OTPState MfaState - MfaMaxSetUp req_model.MFALevel - MfaInitSkipped time.Time + OTPState MFAState + U2FTokens []*WebAuthNView + PasswordlessTokens []*WebAuthNView + MFAMaxSetUp req_model.MFALevel + MFAInitSkipped time.Time InitRequired bool } +type WebAuthNView struct { + TokenID string + Name string + State MFAState +} + type MachineView struct { LastKeyAdded time.Time Name string @@ -108,7 +117,7 @@ func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) { r.Queries = append(r.Queries, &UserSearchQuery{Key: UserSearchKeyResourceOwner, Method: model.SearchMethodEquals, Value: orgID}) } -func (u *UserView) MfaTypesSetupPossible(level req_model.MFALevel, policy *iam_model.LoginPolicyView) []req_model.MFAType { +func (u *UserView) MFATypesSetupPossible(level req_model.MFALevel, policy *iam_model.LoginPolicyView) []req_model.MFAType { types := make([]req_model.MFAType, 0) switch level { default: @@ -118,13 +127,14 @@ func (u *UserView) MfaTypesSetupPossible(level req_model.MFALevel, policy *iam_m for _, mfaType := range policy.SecondFactors { switch mfaType { case iam_model.SecondFactorTypeOTP: - if u.OTPState != MfaStateReady { + if u.OTPState != MFAStateReady { types = append(types, req_model.MFATypeOTP) } + case iam_model.SecondFactorTypeU2F: + types = append(types, req_model.MFATypeU2F) } } } - //PLANNED: add sms fallthrough case req_model.MFALevelMultiFactor: @@ -132,17 +142,15 @@ func (u *UserView) MfaTypesSetupPossible(level req_model.MFALevel, policy *iam_m for _, mfaType := range policy.MultiFactors { switch mfaType { case iam_model.MultiFactorTypeU2FWithPIN: - // TODO: Check if not set up already - // types = append(types, req_model.MFATypeU2F) + types = append(types, req_model.MFATypeU2FUserVerification) } } } - //PLANNED: add token } return types } -func (u *UserView) MfaTypesAllowed(level req_model.MFALevel, policy *iam_model.LoginPolicyView) ([]req_model.MFAType, bool) { +func (u *UserView) MFATypesAllowed(level req_model.MFALevel, policy *iam_model.LoginPolicyView) ([]req_model.MFAType, bool) { types := make([]req_model.MFAType, 0) required := true switch level { @@ -154,9 +162,13 @@ func (u *UserView) MfaTypesAllowed(level req_model.MFALevel, policy *iam_model.L for _, mfaType := range policy.SecondFactors { switch mfaType { case iam_model.SecondFactorTypeOTP: - if u.OTPState == MfaStateReady { + if u.OTPState == MFAStateReady { types = append(types, req_model.MFATypeOTP) } + case iam_model.SecondFactorTypeU2F: + if u.IsU2FReady() { + types = append(types, req_model.MFATypeU2F) + } } } } @@ -167,8 +179,9 @@ func (u *UserView) MfaTypesAllowed(level req_model.MFALevel, policy *iam_model.L for _, mfaType := range policy.MultiFactors { switch mfaType { case iam_model.MultiFactorTypeU2FWithPIN: - // TODO: Check if not set up already - // types = append(types, req_model.MFATypeU2F) + if u.IsPasswordlessReady() { + types = append(types, req_model.MFATypeU2FUserVerification) + } } } } @@ -177,15 +190,33 @@ func (u *UserView) MfaTypesAllowed(level req_model.MFALevel, policy *iam_model.L return types, required } +func (u *UserView) IsU2FReady() bool { + for _, token := range u.U2FTokens { + if token.State == MFAStateReady { + return true + } + } + return false +} + +func (u *UserView) IsPasswordlessReady() bool { + for _, token := range u.PasswordlessTokens { + if token.State == MFAStateReady { + return true + } + } + return false +} + func (u *UserView) HasRequiredOrgMFALevel(policy *iam_model.LoginPolicyView) bool { if !policy.ForceMFA { return true } - switch u.MfaMaxSetUp { + switch u.MFAMaxSetUp { case req_model.MFALevelSecondFactor: return policy.HasSecondFactors() case req_model.MFALevelMultiFactor: - return true + return policy.HasMultiFactors() default: return false } diff --git a/internal/user/model/web_auth_n.go b/internal/user/model/web_auth_n.go new file mode 100644 index 0000000000..9164480aae --- /dev/null +++ b/internal/user/model/web_auth_n.go @@ -0,0 +1,58 @@ +package model + +import ( + "github.com/caos/zitadel/internal/auth_request/model" + es_models "github.com/caos/zitadel/internal/eventstore/models" +) + +type WebAuthNToken struct { + es_models.ObjectRoot + + WebAuthNTokenID string + CredentialCreationData []byte + State MFAState + Challenge string + AllowedCredentialIDs [][]byte + UserVerification UserVerificationRequirement + KeyID []byte + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 + WebAuthNTokenName string +} + +type WebAuthNLogin struct { + es_models.ObjectRoot + + CredentialAssertionData []byte + Challenge string + AllowedCredentialIDs [][]byte + UserVerification UserVerificationRequirement + *model.AuthRequest +} + +type WebAuthNMethod int32 + +const ( + WebAuthNMethodUnspecified WebAuthNMethod = iota + WebAuthNMethodU2F + WebAuthNMethodPasswordless +) + +type UserVerificationRequirement int32 + +const ( + UserVerificationRequirementUnspecified UserVerificationRequirement = iota + UserVerificationRequirementRequired + UserVerificationRequirementPreferred + UserVerificationRequirementDiscouraged +) + +type AuthenticatorAttachment int32 + +const ( + AuthenticatorAttachmentUnspecified AuthenticatorAttachment = iota + AuthenticatorAttachmentPlattform + AuthenticatorAttachmentCrossPlattform +) diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index 05a60c85d3..20fb408a4d 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -21,9 +21,10 @@ import ( iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/id" global_model "github.com/caos/zitadel/internal/model" - "github.com/caos/zitadel/internal/tracing" + "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" + webauthn_helper "github.com/caos/zitadel/internal/webauthn" ) const ( @@ -45,6 +46,7 @@ type UserEventstore struct { MachineKeySize int Multifactors global_model.Multifactors validateTOTP func(string, string) bool + webauthn *webauthn_helper.WebAuthN } type UserConfig struct { @@ -66,9 +68,12 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto emailVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.EmailVerificationCode, aesCrypto) phoneVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PhoneVerificationCode, aesCrypto) passwordVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PasswordVerificationCode, aesCrypto) - aesOtpCrypto, err := crypto.NewAESCrypto(systemDefaults.Multifactors.OTP.VerificationKey) + aesOTPCrypto, err := crypto.NewAESCrypto(systemDefaults.Multifactors.OTP.VerificationKey) passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost) - + web, err := webauthn_helper.StartServer(systemDefaults.WebAuthN.DisplayName, systemDefaults.WebAuthN.ID, systemDefaults.WebAuthN.Origin) + if err != nil { + return nil, err + } return &UserEventstore{ Eventstore: conf.Eventstore, userCache: userCache, @@ -80,7 +85,7 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto PasswordVerificationCode: passwordVerificationCode, Multifactors: global_model.Multifactors{ OTP: global_model.OTP{ - CryptoMFA: aesOtpCrypto, + CryptoMFA: aesOTPCrypto, Issuer: systemDefaults.Multifactors.OTP.Issuer, }, }, @@ -88,6 +93,7 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto validateTOTP: totp.Validate, MachineKeyAlg: aesCrypto, MachineKeySize: int(systemDefaults.SecretGenerators.MachineKeySize), + webauthn: web, }, nil } @@ -109,6 +115,20 @@ func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.U return model.UserToModel(user), nil } +func (es *UserEventstore) HumanByID(ctx context.Context, userID string) (*usr_model.User, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-3M9sf", "Errors.User.UserIDMissing") + } + user, err := es.UserByID(ctx, userID) + if err != nil { + return nil, err + } + if user.Human == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-jLHYG", "Errors.User.NotHuman") + } + return user, nil +} + func (es *UserEventstore) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) { query, err := UserByIDQuery(id, sequence) if err != nil { @@ -404,17 +424,10 @@ func ChangesQuery(userID string, latestSequence, limit uint64, sortAscending boo } func (es *UserEventstore) InitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d8diw", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-mDPtj", "Errors.User.NotHuman") - } - if user.InitCode != nil { return user.InitCode, nil } @@ -422,16 +435,10 @@ func (es *UserEventstore) InitializeUserCodeByID(ctx context.Context, userID str } func (es *UserEventstore) CreateInitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9bbXj", "Errors.User.NotHuman") - } initCode := new(usr_model.InitUserCode) err = initCode.GenerateInitUserCode(es.InitializeUserCode) @@ -452,16 +459,10 @@ func (es *UserEventstore) CreateInitializeUserCodeByID(ctx context.Context, user } func (es *UserEventstore) InitCodeSent(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-0posw", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-SvPa6", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) agg := UserInitCodeSentAggregate(es.AggregateCreator(), repoUser) @@ -474,24 +475,18 @@ func (es *UserEventstore) InitCodeSent(ctx context.Context, userID string) error } func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, verificationCode, password string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.UserIDMissing") + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err } if verificationCode == "" { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.Code.Empty") } pw := &usr_model.Password{SecretString: password} - err := pw.HashPasswordIfExisting(policy, es.PasswordAlg, false) + err = pw.HashPasswordIfExisting(policy, es.PasswordAlg, false) if err != nil { return err } - user, err := es.UserByID(ctx, userID) - if err != nil { - return err - } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-b3xda", "Errors.User.NotHuman") - } if user.InitCode == nil { return caos_errs.ThrowNotFound(nil, "EVENT-spo9W", "Errors.User.Code.NotFound") } @@ -514,20 +509,14 @@ func (es *UserEventstore) VerifyInitCode(ctx context.Context, policy *iam_model. return nil } -func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) +func (es *UserEventstore) SkipMFAInit(ctx context.Context, userID string) error { + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-S1tdl", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) - agg := SkipMfaAggregate(es.AggregateCreator(), repoUser) + agg := SkipMFAAggregate(es.AggregateCreator(), repoUser) err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg) if err != nil { return err @@ -537,16 +526,10 @@ func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error } func (es *UserEventstore) UserPasswordByID(ctx context.Context, userID string) (*usr_model.Password, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-jLHYG", "Errors.User.NotHuman") - } if user.Password != nil { return user.Password, nil @@ -557,13 +540,10 @@ func (es *UserEventstore) UserPasswordByID(ctx context.Context, userID string) ( func (es *UserEventstore) CheckPassword(ctx context.Context, userID, password string, authRequest *req_model.AuthRequest) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-HxcAx", "Errors.User.NotHuman") - } if user.Password == nil { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s35Fa", "Errors.User.Password.Empty") } @@ -594,24 +574,18 @@ func (es *UserEventstore) setPasswordCheckResult(ctx context.Context, user *usr_ } func (es *UserEventstore) SetOneTimePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, password *usr_model.Password) (*usr_model.Password, error) { - user, err := es.UserByID(ctx, password.AggregateID) + user, err := es.HumanByID(ctx, password.AggregateID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-PjDfJ", "Errors.User.NotHuman") - } return es.changedPassword(ctx, user, policy, password.SecretString, true) } func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, code, password string) error { - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-pHkAQ", "Errors.User.NotHuman") - } if user.PasswordCode == nil { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-65sdr", "Errors.User.Code.NotFound") } @@ -623,13 +597,10 @@ func (es *UserEventstore) SetPassword(ctx context.Context, policy *iam_model.Pas } func (es *UserEventstore) ExternalLoginChecked(ctx context.Context, userID string, authRequest *req_model.AuthRequest) error { - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-Gns8i", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) repoAuthRequest := model.AuthRequestFromModel(authRequest) agg := ExternalLoginCheckSucceededAggregate(es.AggregateCreator(), repoUser, repoAuthRequest) @@ -687,13 +658,11 @@ func (es *UserEventstore) ChangeMachine(ctx context.Context, machine *usr_model. func (es *UserEventstore) ChangePassword(ctx context.Context, policy *iam_model.PasswordComplexityPolicyView, userID, old, new string) (_ *usr_model.Password, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - user, err := es.UserByID(ctx, userID) + + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9AuLE", "Errors.User.NotHuman") - } if user.Password == nil { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fds3s", "Errors.User.Password.Empty") } @@ -727,16 +696,10 @@ func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.U } func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string, notifyType usr_model.NotificationType) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-33ywz", "Errors.User.NotHuman") - } if user.State == usr_model.UserStateInitial { return errors.ThrowPreconditionFailed(nil, "EVENT-Hs11s", "Errors.User.NotInitialised") } @@ -787,16 +750,10 @@ func (es *UserEventstore) ResendInitialMail(ctx context.Context, userID, email s } func (es *UserEventstore) PasswordCodeSent(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-s09ow", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-tbVAo", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) agg := PasswordCodeSentAggregate(es.AggregateCreator(), repoUser) @@ -812,13 +769,10 @@ func (es *UserEventstore) AddExternalIDP(ctx context.Context, externalIDP *usr_m if externalIDP == nil || !externalIDP.IsValid() { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Ek9s", "Errors.User.ExternalIDP.Invalid") } - existingUser, err := es.UserByID(ctx, externalIDP.AggregateID) + existingUser, err := es.HumanByID(ctx, externalIDP.AggregateID) if err != nil { return nil, err } - if existingUser.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Cnk8s", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(existingUser) repoExternalIDP := model.ExternalIDPFromModel(externalIDP) aggregates, err := ExternalIDPAddedAggregate(ctx, es.Eventstore.AggregateCreator(), repoUser, repoExternalIDP) @@ -846,13 +800,10 @@ func (es *UserEventstore) BulkAddExternalIDPs(ctx context.Context, userID string return caos_errs.ThrowPreconditionFailed(nil, "EVENT-idue3", "Errors.User.ExternalIDP.Invalid") } } - existingUser, err := es.UserByID(ctx, userID) + existingUser, err := es.HumanByID(ctx, userID) if err != nil { return err } - if existingUser.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-Cnk8s", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(existingUser) repoExternalIDPs := model.ExternalIDPsFromModel(externalIDPs) aggregates, err := ExternalIDPAddedAggregate(ctx, es.Eventstore.AggregateCreator(), repoUser, repoExternalIDPs...) @@ -872,13 +823,10 @@ func (es *UserEventstore) PrepareRemoveExternalIDP(ctx context.Context, external if externalIDP == nil || !externalIDP.IsValid() { return nil, nil, errors.ThrowPreconditionFailed(nil, "EVENT-Cm8sj", "Errors.User.ExternalIDP.Invalid") } - existingUser, err := es.UserByID(ctx, externalIDP.AggregateID) + existingUser, err := es.HumanByID(ctx, externalIDP.AggregateID) if err != nil { return nil, nil, err } - if existingUser.Human == nil { - return nil, nil, errors.ThrowPreconditionFailed(nil, "EVENT-E8iod", "Errors.User.NotHuman") - } _, existingIDP := existingUser.GetExternalIDP(externalIDP) if existingIDP == nil { return nil, nil, errors.ThrowPreconditionFailed(nil, "EVENT-3Dh7s", "Errors.User.ExternalIDP.NotOnUser") @@ -906,16 +854,10 @@ func (es *UserEventstore) RemoveExternalIDP(ctx context.Context, externalIDP *us } func (es *UserEventstore) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-BaE4M", "Errors.User.NotHuman") - } if user.Profile != nil { return user.Profile, nil @@ -928,13 +870,10 @@ func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model. if !profile.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d82i3", "Errors.User.ProfileInvalid") } - user, err := es.UserByID(ctx, profile.AggregateID) + user, err := es.HumanByID(ctx, profile.AggregateID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Xhw8Y", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) repoProfile := model.ProfileFromModel(profile) @@ -950,16 +889,10 @@ func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model. } func (es *UserEventstore) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-zHtOg", "Errors.User.NotHuman") - } if user.Email != nil { return user.Email, nil @@ -971,13 +904,10 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai if !email.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "Errors.User.EmailInvalid") } - user, err := es.UserByID(ctx, email.AggregateID) + user, err := es.HumanByID(ctx, email.AggregateID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-tgBdL", "Errors.User.NotHuman") - } if user.State == usr_model.UserStateInitial { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-3H4q", "Errors.User.NotInitialised") } @@ -1005,18 +935,12 @@ func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Emai } func (es *UserEventstore) VerifyEmail(ctx context.Context, userID, verificationCode string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "Errors.User.UserIDMissing") - } - if verificationCode == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-skDws", "Errors.User.Code.Empty") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-YgXu6", "Errors.User.NotHuman") + if verificationCode == "" { + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-skDws", "Errors.User.Code.Empty") } if user.EmailCode == nil { return caos_errs.ThrowNotFound(nil, "EVENT-lso9w", "Errors.User.Code.NotFound") @@ -1043,16 +967,10 @@ func (es *UserEventstore) setEmailVerifyResult(ctx context.Context, user *usr_mo } func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-hqUZP", "Errors.User.NotHuman") - } if user.State == usr_model.UserStateInitial { return errors.ThrowPreconditionFailed(nil, "EVENT-E3fbw", "Errors.User.NotInitialised") } @@ -1082,16 +1000,10 @@ func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userI } func (es *UserEventstore) EmailVerificationCodeSent(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-spo0w", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-BcFVd", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) agg := EmailCodeSentAggregate(es.AggregateCreator(), repoUser) @@ -1104,16 +1016,10 @@ func (es *UserEventstore) EmailVerificationCodeSent(ctx context.Context, userID } func (es *UserEventstore) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9se", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-LwQeA", "Errors.User.NotHuman") - } if user.Phone != nil { return user.Phone, nil @@ -1125,13 +1031,10 @@ func (es *UserEventstore) ChangePhone(ctx context.Context, phone *usr_model.Phon if !phone.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9s4", "Errors.User.PhoneInvalid") } - user, err := es.UserByID(ctx, phone.AggregateID) + user, err := es.HumanByID(ctx, phone.AggregateID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-oREkn", "Errors.User.NotHuman") - } phoneCode, err := phone.GeneratePhoneCodeIfNeeded(es.PhoneVerificationCode) if err != nil { @@ -1156,13 +1059,10 @@ func (es *UserEventstore) VerifyPhone(ctx context.Context, userID, verificationC if userID == "" || verificationCode == "" { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dsi8s", "Errors.User.UserIDMissing") } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-UspdK", "Errors.User.NotHuman") - } if user.PhoneCode == nil { return caos_errs.ThrowNotFound(nil, "EVENT-slp0s", "Errors.User.Code.NotFound") } @@ -1188,16 +1088,10 @@ func (es *UserEventstore) setPhoneVerifyResult(ctx context.Context, user *usr_mo } func (es *UserEventstore) CreatePhoneVerificationCode(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9sw", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-eEi05", "Errors.User.NotHuman") - } if user.Phone == nil { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp9fs", "Errors.User.PhoneNotFound") } @@ -1224,16 +1118,10 @@ func (es *UserEventstore) CreatePhoneVerificationCode(ctx context.Context, userI } func (es *UserEventstore) PhoneVerificationCodeSent(ctx context.Context, userID string) error { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0wa", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-5bhOP", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) agg := PhoneCodeSentAggregate(es.AggregateCreator(), repoUser) @@ -1246,13 +1134,10 @@ func (es *UserEventstore) PhoneVerificationCodeSent(ctx context.Context, userID } func (es *UserEventstore) RemovePhone(ctx context.Context, userID string) error { - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-Satfl", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) removeAggregate := PhoneRemovedAggregate(es.AggregateCreator(), repoUser) err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, removeAggregate) @@ -1265,16 +1150,10 @@ func (es *UserEventstore) RemovePhone(ctx context.Context, userID string) error } func (es *UserEventstore) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) { - if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di8ws", "Errors.User.UserIDMissing") - } - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-pHrLu", "Errors.User.NotHuman") - } if user.Address != nil { return user.Address, nil @@ -1283,13 +1162,10 @@ func (es *UserEventstore) AddressByID(ctx context.Context, userID string) (*usr_ } func (es *UserEventstore) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) { - user, err := es.UserByID(ctx, address.AggregateID) + user, err := es.HumanByID(ctx, address.AggregateID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-crpHD", "Errors.User.NotHuman") - } repoUser := model.UserFromModel(user) repoAddress := model.AddressFromModel(address) @@ -1304,15 +1180,12 @@ func (es *UserEventstore) ChangeAddress(ctx context.Context, address *usr_model. } func (es *UserEventstore) AddOTP(ctx context.Context, userID, accountName string) (*usr_model.OTP, error) { - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - if user.Human == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-XJvu3", "Errors.User.NotHuman") - } if user.IsOTPReady() { - return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "Errors.User.Mfa.Otp.AlreadyReady") + return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "Errors.User.MFA.OTP.AlreadyReady") } if accountName == "" { accountName = user.UserName @@ -1344,15 +1217,12 @@ func (es *UserEventstore) AddOTP(ctx context.Context, userID, accountName string } func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error { - user, err := es.UserByID(ctx, userID) + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-WsBv9", "Errors.User.NotHuman") - } if user.OTP == nil { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "Errors.User.Mfa.Otp.NotExisting") + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "Errors.User.MFA.OTP.NotExisting") } repoUser := model.UserFromModel(user) updateAggregate := MFAOTPRemoveAggregate(es.AggregateCreator(), repoUser) @@ -1365,21 +1235,18 @@ func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error { return nil } -func (es *UserEventstore) CheckMfaOTPSetup(ctx context.Context, userID, code string) error { - user, err := es.UserByID(ctx, userID) +func (es *UserEventstore) CheckMFAOTPSetup(ctx context.Context, userID, code string) error { + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-7zRQM", "Errors.User.NotHuman") - } if user.OTP == nil { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-yERHV", "Errors.Users.Mfa.Otp.NotExisting") + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-yERHV", "Errors.Users.MFA.OTP.NotExisting") } if user.IsOTPReady() { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-qx4ls", "Errors.Users.Mfa.Otp.AlreadyReady") + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady") } - if err := es.verifyMfaOTP(user.OTP, code); err != nil { + if err := es.verifyMFAOTP(user.OTP, code); err != nil { return err } repoUser := model.UserFromModel(user) @@ -1392,23 +1259,20 @@ func (es *UserEventstore) CheckMfaOTPSetup(ctx context.Context, userID, code str return nil } -func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string, authRequest *req_model.AuthRequest) error { - user, err := es.UserByID(ctx, userID) +func (es *UserEventstore) CheckMFAOTP(ctx context.Context, userID, code string, authRequest *req_model.AuthRequest) error { + user, err := es.HumanByID(ctx, userID) if err != nil { return err } - if user.Human == nil { - return errors.ThrowPreconditionFailed(nil, "EVENT-ckqn5", "Errors.User.NotHuman") - } if !user.IsOTPReady() { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "Errors.User.Mfa.Otp.NotReady") + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sd5NJ", "Errors.User.MFA.OTP.NotReady") } repoUser := model.UserFromModel(user) repoAuthReq := model.AuthRequestFromModel(authRequest) var aggregate func(*es_models.AggregateCreator, *model.User, *model.AuthRequest) es_sdk.AggregateFunc var checkErr error - if checkErr = es.verifyMfaOTP(user.OTP, code); checkErr != nil { + if checkErr = es.verifyMFAOTP(user.OTP, code); checkErr != nil { aggregate = MFAOTPCheckFailedAggregate } else { aggregate = MFAOTPCheckSucceededAggregate @@ -1425,7 +1289,7 @@ func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string, return nil } -func (es *UserEventstore) verifyMfaOTP(otp *usr_model.OTP, code string) error { +func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error { decrypt, err := crypto.DecryptString(otp.Secret, es.Multifactors.OTP.CryptoMFA) if err != nil { return err @@ -1433,11 +1297,222 @@ func (es *UserEventstore) verifyMfaOTP(otp *usr_model.OTP, code string) error { valid := es.validateTOTP(code, decrypt) if !valid { - return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.Mfa.Otp.InvalidCode") + return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.MFA.OTP.InvalidCode") } return nil } +func (es *UserEventstore) AddU2F(ctx context.Context, userID string) (*usr_model.WebAuthNToken, error) { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return nil, err + } + webAuthN, err := es.webauthn.BeginRegistration(user, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, user.U2FTokens...) + if err != nil { + return nil, err + } + tokenID, err := es.idGenerator.Next() + if err != nil { + return nil, err + } + webAuthN.WebAuthNTokenID = tokenID + repoUser := model.UserFromModel(user) + repoWebAuthN := model.WebAuthNFromModel(webAuthN) + + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + if err != nil { + return nil, err + } + return webAuthN, nil +} + +func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + _, token := user.Human.GetU2FToVerify() + webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData) + if err != nil { + return err + } + repoUser := model.UserFromModel(user) + repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + if err != nil { + return err + } + es.userCache.cacheUser(repoUser) + return nil +} + +func (es *UserEventstore) RemoveU2FToken(ctx context.Context, userID, webAuthNTokenID string) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + if _, token := user.Human.GetU2F(webAuthNTokenID); token == nil { + return errors.ThrowPreconditionFailed(nil, "EVENT-2M9ds", "Errors.User.NotHuman") + } + repoUser := model.UserFromModel(user) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FRemoveAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNTokenID{webAuthNTokenID})) + if err != nil { + return err + } + es.userCache.cacheUser(repoUser) + return nil +} + +func (es *UserEventstore) BeginU2FLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest) (*usr_model.WebAuthNLogin, error) { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return nil, err + } + if user.U2FTokens == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting") + } + + webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, user.U2FTokens...) + if err != nil { + return nil, err + } + webAuthNLogin.AuthRequest = authRequest + repoUser := model.UserFromModel(user) + repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) + if err != nil { + return nil, err + } + return webAuthNLogin, nil +} + +func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + _, u2f := user.GetU2FLogin(authRequest.ID) + keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, user.U2FTokens...) + if finishErr != nil && keyID == nil { + return finishErr + } + + _, token := user.GetU2FByKeyID(keyID) + repoUser := model.UserFromModel(user) + repoAuthRequest := model.AuthRequestFromModel(authRequest) + + signAgg := MFAU2FSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) + if err != nil { + return err + } + return finishErr +} + +func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string) (*usr_model.WebAuthNToken, error) { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return nil, err + } + webAuthN, err := es.webauthn.BeginRegistration(user, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, user.PasswordlessTokens...) + if err != nil { + return nil, err + } + tokenID, err := es.idGenerator.Next() + if err != nil { + return nil, err + } + webAuthN.WebAuthNTokenID = tokenID + repoUser := model.UserFromModel(user) + repoWebAuthN := model.WebAuthNFromModel(webAuthN) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + if err != nil { + return nil, err + } + return webAuthN, nil +} + +func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName string, credentialData []byte) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + _, token := user.Human.GetPasswordlessToVerify() + webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData) + if err != nil { + return err + } + repoUser := model.UserFromModel(user) + repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + if err != nil { + return err + } + es.userCache.cacheUser(repoUser) + return nil +} + +func (es *UserEventstore) RemovePasswordlessToken(ctx context.Context, userID, webAuthNTokenID string) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + if _, token := user.Human.GetPasswordless(webAuthNTokenID); token == nil { + return errors.ThrowPreconditionFailed(nil, "EVENT-5M0sw", "Errors.User.NotHuman") + } + repoUser := model.UserFromModel(user) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessRemoveAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNTokenID{webAuthNTokenID})) + if err != nil { + return err + } + es.userCache.cacheUser(repoUser) + return nil +} + +func (es *UserEventstore) BeginPasswordlessLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest) (*usr_model.WebAuthNLogin, error) { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return nil, err + } + if user.PasswordlessTokens == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting") + } + webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, user.PasswordlessTokens...) + if err != nil { + return nil, err + } + webAuthNLogin.AuthRequest = authRequest + repoUser := model.UserFromModel(user) + repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) + if err != nil { + return nil, err + } + return webAuthNLogin, nil +} + +func (es *UserEventstore) VerifyPasswordless(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest) error { + user, err := es.HumanByID(ctx, userID) + if err != nil { + return err + } + _, passwordless := user.GetPasswordlessLogin(authRequest.ID) + keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, user.PasswordlessTokens...) + if finishErr != nil && keyID == nil { + return finishErr + } + _, token := user.GetPasswordlessByKeyID(keyID) + repoUser := model.UserFromModel(user) + repoAuthRequest := model.AuthRequestFromModel(authRequest) + + signAgg := MFAPasswordlessSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) + err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) + if err != nil { + return err + } + return finishErr +} + func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs []string) error { users := make([]*model.User, len(userIDs)) for i, id := range userIDs { diff --git a/internal/user/repository/eventsourcing/eventstore_mock_test.go b/internal/user/repository/eventsourcing/eventstore_mock_test.go index cf222718df..2ea1f973f0 100644 --- a/internal/user/repository/eventsourcing/eventstore_mock_test.go +++ b/internal/user/repository/eventsourcing/eventstore_mock_test.go @@ -442,10 +442,10 @@ func GetMockManipulateUserWithOTP(ctrl *gomock.Controller, decrypt, verified boo }, } dataUser, _ := json.Marshal(user) - dataOtp, _ := json.Marshal(otp) + dataOTP, _ := json.Marshal(otp) events := []*es_models.Event{ {AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.UserAdded, Data: dataUser}, - {AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.MFAOTPAdded, Data: dataOtp}, + {AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.MFAOTPAdded, Data: dataOTP}, } if verified { events = append(events, &es_models.Event{AggregateID: "AggregateID", AggregateVersion: "v1", Sequence: 1, Type: model.MFAOTPVerified}) diff --git a/internal/user/repository/eventsourcing/eventstore_test.go b/internal/user/repository/eventsourcing/eventstore_test.go index 8dbd61a2b0..6eb7b0cc3d 100644 --- a/internal/user/repository/eventsourcing/eventstore_test.go +++ b/internal/user/repository/eventsourcing/eventstore_test.go @@ -1207,7 +1207,7 @@ func TestInitCodeVerify(t *testing.T) { } } -func TestSkipMfaInit(t *testing.T) { +func TestSkipMFAInit(t *testing.T) { ctrl := gomock.NewController(t) type args struct { es *UserEventstore @@ -1256,7 +1256,7 @@ func TestSkipMfaInit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.es.SkipMfaInit(tt.args.ctx, tt.args.user.AggregateID) + err := tt.args.es.SkipMFAInit(tt.args.ctx, tt.args.user.AggregateID) if tt.res.errFunc == nil && err != nil { t.Errorf("rshould not get err") @@ -3479,7 +3479,7 @@ func TestAddOTP(t *testing.T) { } } -func TestCheckMfaOTPSetup(t *testing.T) { +func TestCheckMFAOTPSetup(t *testing.T) { ctrl := gomock.NewController(t) type args struct { es *UserEventstore @@ -3578,7 +3578,7 @@ func TestCheckMfaOTPSetup(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.es.CheckMfaOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code) + err := tt.args.es.CheckMFAOTPSetup(tt.args.ctx, tt.args.userID, tt.args.code) if tt.res.errFunc == nil && err != nil { t.Errorf("result should not get err") @@ -3590,7 +3590,7 @@ func TestCheckMfaOTPSetup(t *testing.T) { } } -func TestCheckMfaOTP(t *testing.T) { +func TestCheckMFAOTP(t *testing.T) { ctrl := gomock.NewController(t) type args struct { es *UserEventstore @@ -3708,7 +3708,7 @@ func TestCheckMfaOTP(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.es.CheckMfaOTP(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.authRequest) + err := tt.args.es.CheckMFAOTP(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.authRequest) if tt.res.errFunc == nil && err != nil { t.Errorf("result should not get err, got : %v", err) diff --git a/internal/user/repository/eventsourcing/model/auth_request.go b/internal/user/repository/eventsourcing/model/auth_request.go index e66d88257c..436199c89d 100644 --- a/internal/user/repository/eventsourcing/model/auth_request.go +++ b/internal/user/repository/eventsourcing/model/auth_request.go @@ -18,12 +18,27 @@ type AuthRequest struct { } func AuthRequestFromModel(request *model.AuthRequest) *AuthRequest { - return &AuthRequest{ + req := &AuthRequest{ ID: request.ID, UserAgentID: request.AgentID, - BrowserInfo: BrowserInfoFromModel(request.BrowserInfo), SelectedIDPConfigID: request.SelectedIDPConfigID, } + if request.BrowserInfo != nil { + req.BrowserInfo = BrowserInfoFromModel(request.BrowserInfo) + } + return req +} + +func AuthRequestToModel(request *AuthRequest) *model.AuthRequest { + req := &model.AuthRequest{ + ID: request.ID, + AgentID: request.UserAgentID, + SelectedIDPConfigID: request.SelectedIDPConfigID, + } + if request.BrowserInfo != nil { + req.BrowserInfo = BrowserInfoToModel(request.BrowserInfo) + } + return req } type BrowserInfo struct { @@ -40,6 +55,13 @@ func BrowserInfoFromModel(info *model.BrowserInfo) *BrowserInfo { } } +func BrowserInfoToModel(info *BrowserInfo) *model.BrowserInfo { + return &model.BrowserInfo{ + UserAgent: info.UserAgent, + AcceptLanguage: info.AcceptLanguage, + RemoteIP: info.RemoteIP, + } +} func (a *AuthRequest) SetData(event *es_models.Event) error { if err := json.Unmarshal(event.Data, a); err != nil { logging.Log("EVEN-T5df6").WithError(err).Error("could not unmarshal event data") diff --git a/internal/user/repository/eventsourcing/model/mfa.go b/internal/user/repository/eventsourcing/model/otp.go similarity index 90% rename from internal/user/repository/eventsourcing/model/mfa.go rename to internal/user/repository/eventsourcing/model/otp.go index f5b18137ee..4b37964ad9 100644 --- a/internal/user/repository/eventsourcing/model/mfa.go +++ b/internal/user/repository/eventsourcing/model/otp.go @@ -2,7 +2,6 @@ package model import ( "encoding/json" - "github.com/caos/logging" "github.com/caos/zitadel/internal/crypto" caos_errs "github.com/caos/zitadel/internal/errors" @@ -29,19 +28,19 @@ func OTPToModel(otp *OTP) *model.OTP { return &model.OTP{ ObjectRoot: otp.ObjectRoot, Secret: otp.Secret, - State: model.MfaState(otp.State), + State: model.MFAState(otp.State), } } func (u *Human) appendOTPAddedEvent(event *es_models.Event) error { u.OTP = &OTP{ - State: int32(model.MfaStateNotReady), + State: int32(model.MFAStateNotReady), } return u.OTP.setData(event) } func (u *Human) appendOTPVerifiedEvent() { - u.OTP.State = int32(model.MfaStateReady) + u.OTP.State = int32(model.MFAStateReady) } func (u *Human) appendOTPRemovedEvent() { diff --git a/internal/user/repository/eventsourcing/model/mfa_test.go b/internal/user/repository/eventsourcing/model/otp_test.go similarity index 90% rename from internal/user/repository/eventsourcing/model/mfa_test.go rename to internal/user/repository/eventsourcing/model/otp_test.go index 3a298036e7..6cbf817262 100644 --- a/internal/user/repository/eventsourcing/model/mfa_test.go +++ b/internal/user/repository/eventsourcing/model/otp_test.go @@ -9,7 +9,7 @@ import ( "github.com/caos/zitadel/internal/user/model" ) -func TestAppendMfaOTPAddedEvent(t *testing.T) { +func TestAppendMFAOTPAddedEvent(t *testing.T) { type args struct { user *Human otp *OTP @@ -27,7 +27,7 @@ func TestAppendMfaOTPAddedEvent(t *testing.T) { otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}, event: &es_models.Event{}, }, - result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MfaStateNotReady)}}, + result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFAStateNotReady)}}, }, } for _, tt := range tests { @@ -44,7 +44,7 @@ func TestAppendMfaOTPAddedEvent(t *testing.T) { } } -func TestAppendMfaOTPVerifyEvent(t *testing.T) { +func TestAppendMFAOTPVerifyEvent(t *testing.T) { type args struct { user *Human otp *OTP @@ -62,7 +62,7 @@ func TestAppendMfaOTPVerifyEvent(t *testing.T) { otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}, event: &es_models.Event{}, }, - result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MfaStateReady)}}, + result: &Human{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFAStateReady)}}, }, } for _, tt := range tests { @@ -79,7 +79,7 @@ func TestAppendMfaOTPVerifyEvent(t *testing.T) { } } -func TestAppendMfaOTPRemoveEvent(t *testing.T) { +func TestAppendMFAOTPRemoveEvent(t *testing.T) { type args struct { user *Human otp *OTP diff --git a/internal/user/repository/eventsourcing/model/types.go b/internal/user/repository/eventsourcing/model/types.go index bc5efdf1a1..f392fd997c 100644 --- a/internal/user/repository/eventsourcing/model/types.go +++ b/internal/user/repository/eventsourcing/model/types.go @@ -118,6 +118,22 @@ const ( HumanMFAOTPCheckFailed models.EventType = "user.human.mfa.otp.check.failed" HumanMFAInitSkipped models.EventType = "user.human.mfa.init.skipped" + HumanMFAU2FTokenAdded models.EventType = "user.human.mfa.u2f.token.added" + HumanMFAU2FTokenVerified models.EventType = "user.human.mfa.u2f.token.verified" + HumanMFAU2FTokenSignCountChanged models.EventType = "user.human.mfa.u2f.token.signcount.changed" + HumanMFAU2FTokenRemoved models.EventType = "user.human.mfa.u2f.token.removed" + HumanMFAU2FTokenBeginLogin models.EventType = "user.human.mfa.u2f.token.begin.login" + HumanMFAU2FTokenCheckSucceeded models.EventType = "user.human.mfa.u2f.token.check.succeeded" + HumanMFAU2FTokenCheckFailed models.EventType = "user.human.mfa.u2f.token.check.failed" + + HumanPasswordlessTokenAdded models.EventType = "user.human.passwordless.token.added" + HumanPasswordlessTokenVerified models.EventType = "user.human.passwordless.token.verified" + HumanPasswordlessTokenChangeSignCount models.EventType = "user.human.passwordless.token.signcount.changed" + HumanPasswordlessTokenRemoved models.EventType = "user.human.passwordless.token.removed" + HumanPasswordlessTokenBeginLogin models.EventType = "user.human.passwordless.token.begin.login" + HumanPasswordlessTokenCheckSucceeded models.EventType = "user.human.passwordless.token.check.succeeded" + HumanPasswordlessTokenCheckFailed models.EventType = "user.human.passwordless.token.check.failed" + HumanSignedOut models.EventType = "user.human.signed.out" ) diff --git a/internal/user/repository/eventsourcing/model/user_human.go b/internal/user/repository/eventsourcing/model/user_human.go index c091cc5b26..36d6e33ac8 100644 --- a/internal/user/repository/eventsourcing/model/user_human.go +++ b/internal/user/repository/eventsourcing/model/user_human.go @@ -19,12 +19,16 @@ type Human struct { *Email *Phone *Address - ExternalIDPs []*ExternalIDP `json:"-"` - InitCode *InitUserCode `json:"-"` - EmailCode *EmailCode `json:"-"` - PhoneCode *PhoneCode `json:"-"` - PasswordCode *PasswordCode `json:"-"` - OTP *OTP `json:"-"` + ExternalIDPs []*ExternalIDP `json:"-"` + InitCode *InitUserCode `json:"-"` + EmailCode *EmailCode `json:"-"` + PhoneCode *PhoneCode `json:"-"` + PasswordCode *PasswordCode `json:"-"` + OTP *OTP `json:"-"` + U2FTokens []*WebAuthNToken `json:"-"` + PasswordlessTokens []*WebAuthNToken `json:"-"` + U2FLogins []*WebAuthNLogin `json:"-"` + PasswordlessLogins []*WebAuthNLogin `json:"-"` } type InitUserCode struct { @@ -56,6 +60,15 @@ func HumanFromModel(user *model.Human) *Human { if user.ExternalIDPs != nil { human.ExternalIDPs = ExternalIDPsFromModel(user.ExternalIDPs) } + if user.U2FTokens != nil { + human.U2FTokens = WebAuthNsFromModel(user.U2FTokens) + } + if user.PasswordlessTokens != nil { + human.PasswordlessTokens = WebAuthNsFromModel(user.PasswordlessTokens) + } + if user.U2FLogins != nil { + human.U2FLogins = WebAuthNLoginsFromModel(user.U2FLogins) + } return human } @@ -94,6 +107,15 @@ func HumanToModel(user *Human) *model.Human { if user.OTP != nil { human.OTP = OTPToModel(user.OTP) } + if user.U2FTokens != nil { + human.U2FTokens = WebAuthNsToModel(user.U2FTokens) + } + if user.PasswordlessTokens != nil { + human.PasswordlessTokens = WebAuthNsToModel(user.PasswordlessTokens) + } + if user.U2FLogins != nil { + human.U2FLogins = WebAuthNLoginsToModel(user.U2FLogins) + } return human } @@ -133,10 +155,10 @@ func (h *Human) AppendEvent(event *es_models.Event) (err error) { HumanAdded, HumanRegistered, HumanProfileChanged: - h.setData(event) + err = h.setData(event) case InitializedUserCodeAdded, InitializedHumanCodeAdded: - h.appendInitUsercodeCreatedEvent(event) + err = h.appendInitUsercodeCreatedEvent(event) case UserPasswordChanged, HumanPasswordChanged: err = h.appendUserPasswordChangedEvent(event) @@ -180,6 +202,26 @@ func (h *Human) AppendEvent(event *es_models.Event) (err error) { err = h.appendExternalIDPAddedEvent(event) case HumanExternalIDPRemoved, HumanExternalIDPCascadeRemoved: err = h.appendExternalIDPRemovedEvent(event) + case HumanMFAU2FTokenAdded: + err = h.appendU2FAddedEvent(event) + case HumanMFAU2FTokenVerified: + err = h.appendU2FVerifiedEvent(event) + case HumanMFAU2FTokenSignCountChanged: + err = h.appendU2FChangeSignCountEvent(event) + case HumanMFAU2FTokenRemoved: + err = h.appendU2FRemovedEvent(event) + case HumanPasswordlessTokenAdded: + err = h.appendPasswordlessAddedEvent(event) + case HumanPasswordlessTokenVerified: + err = h.appendPasswordlessVerifiedEvent(event) + case HumanPasswordlessTokenChangeSignCount: + err = h.appendPasswordlessChangeSignCountEvent(event) + case HumanPasswordlessTokenRemoved: + err = h.appendPasswordlessRemovedEvent(event) + case HumanMFAU2FTokenBeginLogin: + err = h.appendU2FLoginEvent(event) + case HumanPasswordlessTokenBeginLogin: + err = h.appendPasswordlessLoginEvent(event) } if err != nil { return err diff --git a/internal/user/repository/eventsourcing/model/web_auth_n.go b/internal/user/repository/eventsourcing/model/web_auth_n.go new file mode 100644 index 0000000000..f068a92ab0 --- /dev/null +++ b/internal/user/repository/eventsourcing/model/web_auth_n.go @@ -0,0 +1,336 @@ +package model + +import ( + "encoding/json" + + "github.com/caos/logging" + + caos_errs "github.com/caos/zitadel/internal/errors" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/user/model" +) + +type WebAuthNToken struct { + es_models.ObjectRoot + + WebauthNTokenID string `json:"webAuthNTokenId"` + Challenge string `json:"challenge"` + State int32 `json:"-"` + + KeyID []byte `json:"keyId"` + PublicKey []byte `json:"publicKey"` + AttestationType string `json:"attestationType"` + AAGUID []byte `json:"aaguid"` + SignCount uint32 `json:"signCount"` +} + +type WebAuthNVerify struct { + WebAuthNTokenID string `json:"webAuthNTokenId"` + KeyID []byte `json:"keyId"` + PublicKey []byte `json:"publicKey"` + AttestationType string `json:"attestationType"` + AAGUID []byte `json:"aaguid"` + SignCount uint32 `json:"signCount"` + WebAuthNTokenName string `json:"webAuthNTokenName"` +} + +type WebAuthNSignCount struct { + WebauthNTokenID string `json:"webAuthNTokenId"` + SignCount uint32 `json:"signCount"` +} + +type WebAuthNTokenID struct { + WebauthNTokenID string `json:"webAuthNTokenId"` +} + +type WebAuthNLogin struct { + es_models.ObjectRoot + + WebauthNTokenID string `json:"webAuthNTokenId"` + Challenge string `json:"challenge"` + *AuthRequest +} + +func GetWebauthn(webauthnTokens []*WebAuthNToken, id string) (int, *WebAuthNToken) { + for i, webauthn := range webauthnTokens { + if webauthn.WebauthNTokenID == id { + return i, webauthn + } + } + return -1, nil +} + +func WebAuthNsToModel(u2fs []*WebAuthNToken) []*model.WebAuthNToken { + convertedIDPs := make([]*model.WebAuthNToken, len(u2fs)) + for i, m := range u2fs { + convertedIDPs[i] = WebAuthNToModel(m) + } + return convertedIDPs +} + +func WebAuthNsFromModel(u2fs []*model.WebAuthNToken) []*WebAuthNToken { + convertedIDPs := make([]*WebAuthNToken, len(u2fs)) + for i, m := range u2fs { + convertedIDPs[i] = WebAuthNFromModel(m) + } + return convertedIDPs +} + +func WebAuthNFromModel(webAuthN *model.WebAuthNToken) *WebAuthNToken { + return &WebAuthNToken{ + ObjectRoot: webAuthN.ObjectRoot, + WebauthNTokenID: webAuthN.WebAuthNTokenID, + Challenge: webAuthN.Challenge, + State: int32(webAuthN.State), + KeyID: webAuthN.KeyID, + PublicKey: webAuthN.PublicKey, + AAGUID: webAuthN.AAGUID, + SignCount: webAuthN.SignCount, + AttestationType: webAuthN.AttestationType, + } +} + +func WebAuthNToModel(webAuthN *WebAuthNToken) *model.WebAuthNToken { + return &model.WebAuthNToken{ + ObjectRoot: webAuthN.ObjectRoot, + WebAuthNTokenID: webAuthN.WebauthNTokenID, + Challenge: webAuthN.Challenge, + State: model.MFAState(webAuthN.State), + KeyID: webAuthN.KeyID, + PublicKey: webAuthN.PublicKey, + AAGUID: webAuthN.AAGUID, + SignCount: webAuthN.SignCount, + AttestationType: webAuthN.AttestationType, + } +} + +func WebAuthNVerifyFromModel(webAuthN *model.WebAuthNToken) *WebAuthNVerify { + return &WebAuthNVerify{ + WebAuthNTokenID: webAuthN.WebAuthNTokenID, + KeyID: webAuthN.KeyID, + PublicKey: webAuthN.PublicKey, + AAGUID: webAuthN.AAGUID, + SignCount: webAuthN.SignCount, + AttestationType: webAuthN.AttestationType, + WebAuthNTokenName: webAuthN.WebAuthNTokenName, + } +} + +func WebAuthNLoginsToModel(u2fs []*WebAuthNLogin) []*model.WebAuthNLogin { + convertedIDPs := make([]*model.WebAuthNLogin, len(u2fs)) + for i, m := range u2fs { + convertedIDPs[i] = WebAuthNLoginToModel(m) + } + return convertedIDPs +} + +func WebAuthNLoginsFromModel(u2fs []*model.WebAuthNLogin) []*WebAuthNLogin { + convertedIDPs := make([]*WebAuthNLogin, len(u2fs)) + for i, m := range u2fs { + convertedIDPs[i] = WebAuthNLoginFromModel(m) + } + return convertedIDPs +} + +func WebAuthNLoginFromModel(webAuthN *model.WebAuthNLogin) *WebAuthNLogin { + return &WebAuthNLogin{ + ObjectRoot: webAuthN.ObjectRoot, + Challenge: webAuthN.Challenge, + AuthRequest: AuthRequestFromModel(webAuthN.AuthRequest), + } +} + +func WebAuthNLoginToModel(webAuthN *WebAuthNLogin) *model.WebAuthNLogin { + return &model.WebAuthNLogin{ + ObjectRoot: webAuthN.ObjectRoot, + Challenge: webAuthN.Challenge, + AuthRequest: AuthRequestToModel(webAuthN.AuthRequest), + } +} + +func (u *Human) appendU2FAddedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + webauthn.ObjectRoot.CreationDate = event.CreationDate + webauthn.State = int32(model.MFAStateNotReady) + for i, token := range u.U2FTokens { + if token.State == int32(model.MFAStateNotReady) { + u.U2FTokens[i] = webauthn + return nil + } + } + u.U2FTokens = append(u.U2FTokens, webauthn) + return nil +} + +func (u *Human) appendU2FVerifiedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + if _, token := GetWebauthn(u.U2FTokens, webauthn.WebauthNTokenID); token != nil { + err := token.setData(event) + if err != nil { + return err + } + token.State = int32(model.MFAStateReady) + return nil + } + return caos_errs.ThrowPreconditionFailed(nil, "MODEL-4hu9s", "Errors.Users.MFA.U2F.NotExisting") +} + +func (u *Human) appendU2FChangeSignCountEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + if _, token := GetWebauthn(u.U2FTokens, webauthn.WebauthNTokenID); token != nil { + token.setData(event) + return nil + } + return caos_errs.ThrowPreconditionFailed(nil, "MODEL-5Ms8h", "Errors.Users.MFA.U2F.NotExisting") +} + +func (u *Human) appendU2FRemovedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + for i := len(u.U2FTokens) - 1; i >= 0; i-- { + if u.U2FTokens[i].WebauthNTokenID == webauthn.WebauthNTokenID { + copy(u.U2FTokens[i:], u.U2FTokens[i+1:]) + u.U2FTokens[len(u.U2FTokens)-1] = nil + u.U2FTokens = u.U2FTokens[:len(u.U2FTokens)-1] + return nil + } + } + return nil +} + +func (u *Human) appendPasswordlessAddedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + webauthn.ObjectRoot.CreationDate = event.CreationDate + webauthn.State = int32(model.MFAStateNotReady) + for i, token := range u.PasswordlessTokens { + if token.State == int32(model.MFAStateNotReady) { + u.PasswordlessTokens[i] = webauthn + return nil + } + } + u.PasswordlessTokens = append(u.PasswordlessTokens, webauthn) + return nil +} + +func (u *Human) appendPasswordlessVerifiedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + if _, token := GetWebauthn(u.PasswordlessTokens, webauthn.WebauthNTokenID); token != nil { + err := token.setData(event) + if err != nil { + return err + } + token.State = int32(model.MFAStateReady) + return nil + } + return caos_errs.ThrowPreconditionFailed(nil, "MODEL-mKns8", "Errors.Users.MFA.Passwordless.NotExisting") +} + +func (u *Human) appendPasswordlessChangeSignCountEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + if _, token := GetWebauthn(u.PasswordlessTokens, webauthn.WebauthNTokenID); token != nil { + err := token.setData(event) + if err != nil { + return err + } + return nil + } + return caos_errs.ThrowPreconditionFailed(nil, "MODEL-2Mv9s", "Errors.Users.MFA.Passwordless.NotExisting") +} + +func (u *Human) appendPasswordlessRemovedEvent(event *es_models.Event) error { + webauthn := new(WebAuthNToken) + err := webauthn.setData(event) + if err != nil { + return err + } + for i := len(u.PasswordlessTokens) - 1; i >= 0; i-- { + if u.PasswordlessTokens[i].WebauthNTokenID == webauthn.WebauthNTokenID { + copy(u.PasswordlessTokens[i:], u.PasswordlessTokens[i+1:]) + u.PasswordlessTokens[len(u.PasswordlessTokens)-1] = nil + u.PasswordlessTokens = u.PasswordlessTokens[:len(u.PasswordlessTokens)-1] + return nil + } + } + return nil +} + +func (w *WebAuthNToken) setData(event *es_models.Event) error { + w.ObjectRoot.AppendEvent(event) + if err := json.Unmarshal(event.Data, w); err != nil { + logging.Log("EVEN-4M9is").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event") + } + return nil +} + +func (u *Human) appendU2FLoginEvent(event *es_models.Event) error { + webauthn := new(WebAuthNLogin) + webauthn.ObjectRoot.AppendEvent(event) + err := webauthn.setData(event) + if err != nil { + return err + } + webauthn.ObjectRoot.CreationDate = event.CreationDate + for i, token := range u.U2FLogins { + if token.AuthRequest.ID == webauthn.AuthRequest.ID { + u.U2FLogins[i] = webauthn + return nil + } + } + u.U2FLogins = append(u.U2FLogins, webauthn) + return nil +} + +func (u *Human) appendPasswordlessLoginEvent(event *es_models.Event) error { + webauthn := new(WebAuthNLogin) + webauthn.ObjectRoot.AppendEvent(event) + err := webauthn.setData(event) + if err != nil { + return err + } + webauthn.ObjectRoot.CreationDate = event.CreationDate + for i, token := range u.PasswordlessLogins { + if token.AuthRequest.ID == webauthn.AuthRequest.ID { + u.PasswordlessLogins[i] = webauthn + return nil + } + } + u.PasswordlessLogins = append(u.PasswordlessLogins, webauthn) + return nil +} + +func (w *WebAuthNLogin) setData(event *es_models.Event) error { + w.ObjectRoot.AppendEvent(event) + if err := json.Unmarshal(event.Data, w); err != nil { + logging.Log("EVEN-hmSlo").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event") + } + return nil +} diff --git a/internal/user/repository/eventsourcing/model/web_auth_n_test.go b/internal/user/repository/eventsourcing/model/web_auth_n_test.go new file mode 100644 index 0000000000..b4b82b1bab --- /dev/null +++ b/internal/user/repository/eventsourcing/model/web_auth_n_test.go @@ -0,0 +1,151 @@ +package model + +import ( + "encoding/json" + "github.com/caos/zitadel/pkg/grpc/auth" + "testing" + + es_models "github.com/caos/zitadel/internal/eventstore/models" +) + +func TestAppendMFAU2FAddedEvent(t *testing.T) { + type args struct { + user *Human + u2f *WebAuthNToken + event *es_models.Event + } + tests := []struct { + name string + args args + result *Human + }{ + { + name: "append user u2f event", + args: args{ + user: &Human{}, + u2f: &WebAuthNToken{WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge"}, + event: &es_models.Event{}, + }, + result: &Human{ + U2FTokens: []*WebAuthNToken{ + {WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge", State: int32(auth.MFAState_MFASTATE_NOT_READY)}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.u2f != nil { + data, _ := json.Marshal(tt.args.u2f) + tt.args.event.Data = data + } + tt.args.user.appendU2FAddedEvent(tt.args.event) + if tt.args.user.U2FTokens[0].State != tt.result.U2FTokens[0].State { + t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].State, tt.args.user.U2FTokens[0].State) + } + }) + } +} + +func TestAppendMFAU2FVerifyEvent(t *testing.T) { + type args struct { + user *Human + u2f *WebAuthNVerify + event *es_models.Event + } + tests := []struct { + name string + args args + result *Human + }{ + { + name: "append u2f verify event", + args: args{ + user: &Human{ + U2FTokens: []*WebAuthNToken{ + {WebauthNTokenID: "WebauthNTokenID", Challenge: "Challenge", State: int32(auth.MFAState_MFASTATE_NOT_READY)}, + }, + }, + u2f: &WebAuthNVerify{WebAuthNTokenID: "WebauthNTokenID", KeyID: []byte("KeyID"), PublicKey: []byte("PublicKey"), AttestationType: "AttestationType", AAGUID: []byte("AAGUID"), SignCount: 1}, + event: &es_models.Event{}, + }, + result: &Human{ + U2FTokens: []*WebAuthNToken{ + { + WebauthNTokenID: "WebauthNTokenID", + Challenge: "Challenge", + State: int32(auth.MFAState_MFASTATE_READY), + KeyID: []byte("KeyID"), + PublicKey: []byte("PublicKey"), + AttestationType: "AttestationType", + AAGUID: []byte("AAGUID"), + SignCount: 1, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.u2f != nil { + data, _ := json.Marshal(tt.args.u2f) + tt.args.event.Data = data + } + tt.args.user.appendU2FVerifiedEvent(tt.args.event) + if tt.args.user.U2FTokens[0].State != tt.result.U2FTokens[0].State { + t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].State, tt.args.user.U2FTokens[0].State) + } + if tt.args.user.U2FTokens[0].AttestationType != tt.result.U2FTokens[0].AttestationType { + t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.U2FTokens[0].AttestationType, tt.args.user.U2FTokens[0].AttestationType) + } + }) + } +} + +func TestAppendMFAU2FRemoveEvent(t *testing.T) { + type args struct { + user *Human + u2f *WebAuthNTokenID + event *es_models.Event + } + tests := []struct { + name string + args args + result *Human + }{ + { + name: "append u2f remove event", + args: args{ + user: &Human{ + U2FTokens: []*WebAuthNToken{ + { + WebauthNTokenID: "WebauthNTokenID", + Challenge: "Challenge", + State: int32(auth.MFAState_MFASTATE_NOT_READY), + KeyID: []byte("KeyID"), + PublicKey: []byte("PublicKey"), + AttestationType: "AttestationType", + AAGUID: []byte("AAGUID"), + SignCount: 1, + }, + }, + }, + u2f: &WebAuthNTokenID{WebauthNTokenID: "WebauthNTokenID"}, + event: &es_models.Event{}, + }, + result: &Human{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.u2f != nil { + data, _ := json.Marshal(tt.args.u2f) + tt.args.event.Data = data + } + tt.args.user.appendU2FRemovedEvent(tt.args.event) + if len(tt.args.user.U2FTokens) != 0 { + t.Errorf("got wrong result: actual: %v ", tt.result.U2FTokens) + } + }) + } +} diff --git a/internal/user/repository/eventsourcing/user.go b/internal/user/repository/eventsourcing/user.go index 2c5f568dfc..b4ed0bdf78 100644 --- a/internal/user/repository/eventsourcing/user.go +++ b/internal/user/repository/eventsourcing/user.go @@ -400,7 +400,7 @@ func InitCodeCheckFailedAggregate(aggCreator *es_models.AggregateCreator, user * } } -func SkipMfaAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) { +func SkipMFAAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := UserAggregate(ctx, aggCreator, user) if err != nil { @@ -773,7 +773,7 @@ func MFAOTPCheckFailedAggregate(aggCreator *es_models.AggregateCreator, user *mo } } -func MFAOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) { +func MFAOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.User) es_sdk.AggregateFunc { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := UserAggregate(ctx, aggCreator, user) if err != nil { @@ -783,6 +783,132 @@ func MFAOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.U } } +func MFAU2FAddAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNToken) es_sdk.AggregateFunc { + return MFAWebauthNAddAggregate(aggCreator, user, webauthN, model.HumanMFAU2FTokenAdded) +} + +func MFAPasswordlessAddAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNToken) es_sdk.AggregateFunc { + return MFAWebauthNAddAggregate(aggCreator, user, webauthN, model.HumanPasswordlessTokenAdded) +} + +func MFAWebauthNAddAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNToken, event es_models.EventType) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4N90s", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + return agg.AppendEvent(event, webauthN) + } +} + +func MFAU2FVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNVerify) es_sdk.AggregateFunc { + return MFAWebauthNVerifyAggregate(aggCreator, user, webauthN, model.HumanMFAU2FTokenVerified) +} + +func MFAPasswordlessVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNVerify) es_sdk.AggregateFunc { + return MFAWebauthNVerifyAggregate(aggCreator, user, webauthN, model.HumanPasswordlessTokenVerified) +} + +func MFAWebauthNVerifyAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNVerify, event es_models.EventType) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4N90s", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + return agg.AppendEvent(event, webauthN) + } +} + +func MFAU2FSignCountAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNSignCount, check *model.AuthRequest, checkSucceeded bool) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4N90s", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + agg, err = agg.AppendEvent(model.HumanMFAU2FTokenSignCountChanged, webauthN) + if err != nil { + return nil, err + } + if checkSucceeded { + return agg.AppendEvent(model.HumanMFAU2FTokenCheckSucceeded, check) + } else { + return agg.AppendEvent(model.HumanMFAU2FTokenCheckFailed, check) + } + } +} + +func MFAPasswordlessSignCountAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNSignCount, check *model.AuthRequest, checkSucceeded bool) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4N90s", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + agg, err = agg.AppendEvent(model.HumanPasswordlessTokenChangeSignCount, webauthN) + if err != nil { + return nil, err + } + if checkSucceeded { + return agg.AppendEvent(model.HumanPasswordlessTokenCheckSucceeded, check) + } else { + return agg.AppendEvent(model.HumanPasswordlessTokenCheckFailed, check) + } + } +} + +func MFAU2FRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNTokenID) es_sdk.AggregateFunc { + return MFAWebauthNRemoveAggregate(aggCreator, user, webauthN, model.HumanMFAU2FTokenRemoved) +} + +func MFAPasswordlessRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNTokenID) es_sdk.AggregateFunc { + return MFAWebauthNRemoveAggregate(aggCreator, user, webauthN, model.HumanPasswordlessTokenRemoved) +} + +func MFAWebauthNRemoveAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNTokenID, event es_models.EventType) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4Ms9l", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + return agg.AppendEvent(event, webauthN) + } +} + +func MFAU2FBeginLoginAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNLogin) es_sdk.AggregateFunc { + return MFAWebauthNBeginLoginAggregate(aggCreator, user, webauthN, model.HumanMFAU2FTokenBeginLogin) +} + +func MFAPasswordlessBeginLoginAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNLogin) es_sdk.AggregateFunc { + return MFAWebauthNBeginLoginAggregate(aggCreator, user, webauthN, model.HumanPasswordlessTokenBeginLogin) +} + +func MFAWebauthNBeginLoginAggregate(aggCreator *es_models.AggregateCreator, user *model.User, webauthN *model.WebAuthNLogin, event es_models.EventType) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if webauthN == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4N90s", "Errors.Internal") + } + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + return agg.AppendEvent(event, webauthN) + } +} + func SignOutAggregates(aggCreator *es_models.AggregateCreator, users []*model.User, agentID string) func(ctx context.Context) ([]*es_models.Aggregate, error) { return func(ctx context.Context) ([]*es_models.Aggregate, error) { aggregates := make([]*es_models.Aggregate, len(users)) diff --git a/internal/user/repository/eventsourcing/user_test.go b/internal/user/repository/eventsourcing/user_test.go index bd7000a55c..328828598e 100644 --- a/internal/user/repository/eventsourcing/user_test.go +++ b/internal/user/repository/eventsourcing/user_test.go @@ -991,7 +991,7 @@ func TestInitCodeCheckFailedAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := SkipMfaAggregate(tt.args.aggCreator, tt.args.user)(tt.args.ctx) + agg, err := SkipMFAAggregate(tt.args.aggCreator, tt.args.user)(tt.args.ctx) if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) @@ -1006,7 +1006,7 @@ func TestInitCodeCheckFailedAggregate(t *testing.T) { } } -func TestSkipMfaAggregate(t *testing.T) { +func TestSkipMFAAggregate(t *testing.T) { type args struct { ctx context.Context user *model.User diff --git a/internal/user/repository/view/model/user.go b/internal/user/repository/view/model/user.go index f0406eddab..b13031bc5e 100644 --- a/internal/user/repository/view/model/user.go +++ b/internal/user/repository/view/model/user.go @@ -1,18 +1,18 @@ package model import ( + "database/sql/driver" "encoding/json" - iam_model "github.com/caos/zitadel/internal/iam/model" "time" - org_model "github.com/caos/zitadel/internal/org/model" - "github.com/lib/pq" - "github.com/caos/logging" + "github.com/lib/pq" req_model "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" + iam_model "github.com/caos/zitadel/internal/iam/model" + org_model "github.com/caos/zitadel/internal/org/model" "github.com/caos/zitadel/internal/user/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" ) @@ -67,30 +67,57 @@ const ( ) type HumanView struct { - FirstName string `json:"firstName" gorm:"column:first_name"` - LastName string `json:"lastName" gorm:"column:last_name"` - NickName string `json:"nickName" gorm:"column:nick_name"` - DisplayName string `json:"displayName" gorm:"column:display_name"` - PreferredLanguage string `json:"preferredLanguage" gorm:"column:preferred_language"` - Gender int32 `json:"gender" gorm:"column:gender"` - Email string `json:"email" gorm:"column:email"` - IsEmailVerified bool `json:"-" gorm:"column:is_email_verified"` - Phone string `json:"phone" gorm:"column:phone"` - IsPhoneVerified bool `json:"-" gorm:"column:is_phone_verified"` - Country string `json:"country" gorm:"column:country"` - Locality string `json:"locality" gorm:"column:locality"` - PostalCode string `json:"postalCode" gorm:"column:postal_code"` - Region string `json:"region" gorm:"column:region"` - StreetAddress string `json:"streetAddress" gorm:"column:street_address"` - OTPState int32 `json:"-" gorm:"column:otp_state"` - MfaMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"` - MfaInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"` - InitRequired bool `json:"-" gorm:"column:init_required"` + FirstName string `json:"firstName" gorm:"column:first_name"` + LastName string `json:"lastName" gorm:"column:last_name"` + NickName string `json:"nickName" gorm:"column:nick_name"` + DisplayName string `json:"displayName" gorm:"column:display_name"` + PreferredLanguage string `json:"preferredLanguage" gorm:"column:preferred_language"` + Gender int32 `json:"gender" gorm:"column:gender"` + Email string `json:"email" gorm:"column:email"` + IsEmailVerified bool `json:"-" gorm:"column:is_email_verified"` + Phone string `json:"phone" gorm:"column:phone"` + IsPhoneVerified bool `json:"-" gorm:"column:is_phone_verified"` + Country string `json:"country" gorm:"column:country"` + Locality string `json:"locality" gorm:"column:locality"` + PostalCode string `json:"postalCode" gorm:"column:postal_code"` + Region string `json:"region" gorm:"column:region"` + StreetAddress string `json:"streetAddress" gorm:"column:street_address"` + OTPState int32 `json:"-" gorm:"column:otp_state"` + U2FTokens WebAuthNTokens `json:"-" gorm:"column:u2f_tokens"` + MFAMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"` + MFAInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"` + InitRequired bool `json:"-" gorm:"column:init_required"` - PasswordSet bool `json:"-" gorm:"column:password_set"` - PasswordChangeRequired bool `json:"-" gorm:"column:password_change_required"` - UsernameChangeRequired bool `json:"-" gorm:"column:username_change_required"` - PasswordChanged time.Time `json:"-" gorm:"column:password_change"` + PasswordSet bool `json:"-" gorm:"column:password_set"` + PasswordChangeRequired bool `json:"-" gorm:"column:password_change_required"` + UsernameChangeRequired bool `json:"-" gorm:"column:username_change_required"` + PasswordChanged time.Time `json:"-" gorm:"column:password_change"` + PasswordlessTokens WebAuthNTokens `json:"-" gorm:"column:passwordless_tokens"` +} + +type WebAuthNTokens []*WebAuthNView + +type WebAuthNView struct { + ID string `json:"webAuthNTokenId"` + Name string `json:"webAuthNTokenName,omitempty"` + State int32 `json:"state,omitempty"` +} + +func (t WebAuthNTokens) Value() (driver.Value, error) { + if t == nil { + return nil, nil + } + return json.Marshal(&t) +} + +func (t *WebAuthNTokens) Scan(src interface{}) error { + if b, ok := src.([]byte); ok { + return json.Unmarshal(b, t) + } + if s, ok := src.(string); ok { + return json.Unmarshal([]byte(s), t) + } + return nil } func (h *HumanView) IsZero() bool { @@ -124,6 +151,8 @@ func UserToModel(user *UserView) *model.UserView { PasswordSet: user.PasswordSet, PasswordChangeRequired: user.PasswordChangeRequired, PasswordChanged: user.PasswordChanged, + PasswordlessTokens: WebauthnTokensToModel(user.PasswordlessTokens), + U2FTokens: WebauthnTokensToModel(user.U2FTokens), FirstName: user.FirstName, LastName: user.LastName, NickName: user.NickName, @@ -139,9 +168,9 @@ func UserToModel(user *UserView) *model.UserView { PostalCode: user.PostalCode, Region: user.Region, StreetAddress: user.StreetAddress, - OTPState: model.MfaState(user.OTPState), - MfaMaxSetUp: req_model.MFALevel(user.MfaMaxSetUp), - MfaInitSkipped: user.MfaInitSkipped, + OTPState: model.MFAState(user.OTPState), + MFAMaxSetUp: req_model.MFALevel(user.MFAMaxSetUp), + MFAInitSkipped: user.MFAInitSkipped, InitRequired: user.InitRequired, } } @@ -163,6 +192,25 @@ func UsersToModel(users []*UserView) []*model.UserView { return result } +func WebauthnTokensToModel(tokens []*WebAuthNView) []*model.WebAuthNView { + if tokens == nil { + return nil + } + result := make([]*model.WebAuthNView, len(tokens)) + for i, t := range tokens { + result[i] = WebauthnTokenToModel(t) + } + return result +} + +func WebauthnTokenToModel(token *WebAuthNView) *model.WebAuthNView { + return &model.WebAuthNView{ + TokenID: token.ID, + Name: token.Name, + State: model.MFAState(token.State), + } +} + func (u *UserView) GenerateLoginName(domain string, appendDomain bool) string { if !appendDomain { return u.UserName @@ -212,6 +260,12 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) { case es_model.UserPasswordChanged, es_model.HumanPasswordChanged: err = u.setPasswordData(event) + case es_model.HumanPasswordlessTokenAdded: + err = u.addPasswordlessToken(event) + case es_model.HumanPasswordlessTokenVerified: + err = u.updatePasswordlessToken(event) + case es_model.HumanPasswordlessTokenRemoved: + err = u.removePasswordlessToken(event) case es_model.UserProfileChanged, es_model.HumanProfileChanged, es_model.UserAddressChanged, @@ -251,17 +305,27 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) { u.State = int32(model.UserStateLocked) case es_model.MFAOTPAdded, es_model.HumanMFAOTPAdded: - u.OTPState = int32(model.MfaStateNotReady) + u.OTPState = int32(model.MFAStateNotReady) case es_model.MFAOTPVerified, es_model.HumanMFAOTPVerified: - u.OTPState = int32(model.MfaStateReady) - u.MfaInitSkipped = time.Time{} + u.OTPState = int32(model.MFAStateReady) + u.MFAInitSkipped = time.Time{} case es_model.MFAOTPRemoved, es_model.HumanMFAOTPRemoved: - u.OTPState = int32(model.MfaStateUnspecified) + u.OTPState = int32(model.MFAStateUnspecified) + case es_model.HumanMFAU2FTokenAdded: + err = u.addU2FToken(event) + case es_model.HumanMFAU2FTokenVerified: + err = u.updateU2FToken(event) + if err != nil { + return err + } + u.MFAInitSkipped = time.Time{} + case es_model.HumanMFAU2FTokenRemoved: + err = u.removeU2FToken(event) case es_model.MFAInitSkipped, es_model.HumanMFAInitSkipped: - u.MfaInitSkipped = event.CreationDate + u.MFAInitSkipped = event.CreationDate case es_model.InitializedUserCodeAdded, es_model.InitializedHumanCodeAdded: u.InitRequired = true @@ -298,6 +362,106 @@ func (u *UserView) setPasswordData(event *models.Event) error { return nil } +func (u *UserView) addPasswordlessToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for _, t := range u.PasswordlessTokens { + if t.State == int32(model.MFAStateNotReady) { + t = token + return nil + } + } + u.U2FTokens = append(u.U2FTokens, token) + return nil +} + +func (u *UserView) updatePasswordlessToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for i, t := range u.PasswordlessTokens { + if t.ID == token.ID { + u.PasswordlessTokens[i].Name = token.Name + u.PasswordlessTokens[i].State = int32(model.MFAStateReady) + return nil + } + } + return nil +} + +func (u *UserView) removePasswordlessToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for i, t := range u.PasswordlessTokens { + if t.ID == token.ID { + u.PasswordlessTokens[i] = u.PasswordlessTokens[len(u.PasswordlessTokens)-1] + u.PasswordlessTokens[len(u.PasswordlessTokens)-1] = nil + u.PasswordlessTokens = u.PasswordlessTokens[:len(u.PasswordlessTokens)-1] + return nil + } + } + return nil +} + +func (u *UserView) addU2FToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for _, t := range u.U2FTokens { + if t.State == int32(model.MFAStateNotReady) { + t = token + return nil + } + } + u.U2FTokens = append(u.U2FTokens, token) + return nil +} + +func (u *UserView) updateU2FToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for i, t := range u.U2FTokens { + if t.ID == token.ID { + u.U2FTokens[i].Name = token.Name + u.U2FTokens[i].State = int32(model.MFAStateReady) + return nil + } + } + return nil +} + +func (u *UserView) removeU2FToken(event *models.Event) error { + token, err := webAuthNViewFromEvent(event) + if err != nil { + return err + } + for i := len(u.U2FTokens) - 1; i >= 0; i-- { + if u.U2FTokens[i].ID == token.ID { + u.U2FTokens[i] = u.U2FTokens[len(u.U2FTokens)-1] + u.U2FTokens[len(u.U2FTokens)-1] = nil + u.U2FTokens = u.U2FTokens[:len(u.U2FTokens)-1] + } + } + return nil +} + +func webAuthNViewFromEvent(event *models.Event) (*WebAuthNView, error) { + token := new(WebAuthNView) + err := json.Unmarshal(event.Data, token) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "MODEL-FSaq1", "could not unmarshal data") + } + return token, err +} + func (u *UserView) ComputeObject() { if !u.MachineView.IsZero() { if u.State == int32(model.UserStateUnspecified) { @@ -312,10 +476,25 @@ func (u *UserView) ComputeObject() { u.State = int32(model.UserStateInitial) } } - if u.OTPState != int32(model.MfaStateReady) { - u.MfaMaxSetUp = int32(req_model.MFALevelNotSetUp) - } - if u.OTPState == int32(model.MfaStateReady) { - u.MfaMaxSetUp = int32(req_model.MFALevelSecondFactor) - } + u.ComputeMFAMaxSetUp() +} + +func (u *UserView) ComputeMFAMaxSetUp() { + for _, token := range u.PasswordlessTokens { + if token.State == int32(model.MFAStateReady) { + u.MFAMaxSetUp = int32(req_model.MFALevelMultiFactor) + return + } + } + for _, token := range u.U2FTokens { + if token.State == int32(model.MFAStateReady) { + u.MFAMaxSetUp = int32(req_model.MFALevelSecondFactor) + return + } + } + if u.OTPState == int32(model.MFAStateReady) { + u.MFAMaxSetUp = int32(req_model.MFALevelSecondFactor) + return + } + u.MFAMaxSetUp = int32(req_model.MFALevelNotSetUp) } diff --git a/internal/user/repository/view/model/user_session.go b/internal/user/repository/view/model/user_session.go index e2f0f8c5c6..f213c3505c 100644 --- a/internal/user/repository/view/model/user_session.go +++ b/internal/user/repository/view/model/user_session.go @@ -32,6 +32,7 @@ type UserSessionView struct { DisplayName string `json:"-" gorm:"column:user_display_name"` SelectedIDPConfigID string `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"` PasswordVerification time.Time `json:"-" gorm:"column:password_verification"` + PasswordlessVerification time.Time `json:"-" gorm:"column:passwordless_verification"` ExternalLoginVerification time.Time `json:"-" gorm:"column:external_login_verification"` SecondFactorVerification time.Time `json:"-" gorm:"column:second_factor_verification"` SecondFactorVerificationType int32 `json:"-" gorm:"column:second_factor_verification_type"` @@ -62,6 +63,7 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView { DisplayName: userSession.DisplayName, SelectedIDPConfigID: userSession.SelectedIDPConfigID, PasswordVerification: userSession.PasswordVerification, + PasswordlessVerification: userSession.PasswordlessVerification, ExternalLoginVerification: userSession.ExternalLoginVerification, SecondFactorVerification: userSession.SecondFactorVerification, SecondFactorVerificationType: req_model.MFAType(userSession.SecondFactorVerificationType), @@ -93,6 +95,15 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { v.ExternalLoginVerification = event.CreationDate v.SelectedIDPConfigID = data.SelectedIDPConfigID v.State = int32(req_model.UserSessionStateActive) + case es_model.HumanPasswordlessTokenCheckSucceeded: + v.PasswordlessVerification = event.CreationDate + v.MultiFactorVerification = event.CreationDate + v.MultiFactorVerificationType = int32(req_model.MFATypeU2FUserVerification) + v.State = int32(req_model.UserSessionStateActive) + case es_model.HumanPasswordlessTokenCheckFailed, + es_model.HumanPasswordlessTokenRemoved: + v.PasswordlessVerification = time.Time{} + v.MultiFactorVerification = time.Time{} case es_model.UserPasswordCheckFailed, es_model.UserPasswordChanged, es_model.HumanPasswordCheckFailed, @@ -106,8 +117,14 @@ func (v *UserSessionView) AppendEvent(event *models.Event) { case es_model.MFAOTPCheckFailed, es_model.MFAOTPRemoved, es_model.HumanMFAOTPCheckFailed, - es_model.HumanMFAOTPRemoved: + es_model.HumanMFAOTPRemoved, + es_model.HumanMFAU2FTokenCheckFailed, + es_model.HumanMFAU2FTokenRemoved: v.SecondFactorVerification = time.Time{} + case es_model.HumanMFAU2FTokenCheckSucceeded: + v.SecondFactorVerification = event.CreationDate + v.SecondFactorVerificationType = int32(req_model.MFATypeU2F) + v.State = int32(req_model.UserSessionStateActive) case es_model.SignedOut, es_model.HumanSignedOut, es_model.UserLocked, diff --git a/internal/user/repository/view/model/user_test.go b/internal/user/repository/view/model/user_test.go index 853a1e55b0..41e91203cd 100644 --- a/internal/user/repository/view/model/user_test.go +++ b/internal/user/repository/view/model/user_test.go @@ -305,7 +305,7 @@ func TestUserAppendEvent(t *testing.T) { event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.MFAOTPAdded, ResourceOwner: "OrgID"}, user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country"}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateNotReady)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateNotReady)}, State: int32(model.UserStateActive)}, }, { name: "append human add otp event", @@ -313,39 +313,39 @@ func TestUserAppendEvent(t *testing.T) { event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.HumanMFAOTPAdded, ResourceOwner: "OrgID"}, user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country"}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateNotReady)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateNotReady)}, State: int32(model.UserStateActive)}, }, { name: "append user verify otp event", args: args{ event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.MFAOTPVerified, ResourceOwner: "OrgID"}, - user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateNotReady)}, State: int32(model.UserStateActive)}, + user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateNotReady)}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateReady)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateReady)}, State: int32(model.UserStateActive)}, }, { name: "append human verify otp event", args: args{ event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.HumanMFAOTPVerified, ResourceOwner: "OrgID"}, - user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateNotReady)}, State: int32(model.UserStateActive)}, + user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateNotReady)}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateReady)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateReady)}, State: int32(model.UserStateActive)}, }, { name: "append user remove otp event", args: args{ event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.MFAOTPRemoved, ResourceOwner: "OrgID"}, - user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateReady)}, State: int32(model.UserStateActive)}, + user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateReady)}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateUnspecified)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateUnspecified)}, State: int32(model.UserStateActive)}, }, { name: "append human remove otp event", args: args{ event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: es_model.HumanMFAOTPRemoved, ResourceOwner: "OrgID"}, - user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateReady)}, State: int32(model.UserStateActive)}, + user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateReady)}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MfaStateUnspecified)}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", OTPState: int32(model.MFAStateUnspecified)}, State: int32(model.UserStateActive)}, }, { name: "append user mfa init skipped event", @@ -353,7 +353,7 @@ func TestUserAppendEvent(t *testing.T) { event: &es_models.Event{Sequence: 1, CreationDate: time.Now().UTC(), Type: es_model.MFAInitSkipped, AggregateID: "AggregateID", ResourceOwner: "OrgID"}, user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country"}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", MfaInitSkipped: time.Now().UTC()}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", MFAInitSkipped: time.Now().UTC()}, State: int32(model.UserStateActive)}, }, { name: "append human mfa init skipped event", @@ -361,7 +361,7 @@ func TestUserAppendEvent(t *testing.T) { event: &es_models.Event{Sequence: 1, CreationDate: time.Now().UTC(), Type: es_model.HumanMFAInitSkipped, AggregateID: "AggregateID", ResourceOwner: "OrgID"}, user: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country"}, State: int32(model.UserStateActive)}, }, - result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", MfaInitSkipped: time.Now().UTC()}, State: int32(model.UserStateActive)}, + result: &UserView{ID: "AggregateID", ResourceOwner: "OrgID", UserName: "UserName", HumanView: &HumanView{FirstName: "FirstName", LastName: "LastName", Email: "Email", Phone: "Phone", Country: "Country", MFAInitSkipped: time.Now().UTC()}, State: int32(model.UserStateActive)}, }, } for _, tt := range tests { @@ -401,8 +401,8 @@ func TestUserAppendEvent(t *testing.T) { if human.OTPState != tt.result.OTPState { t.Errorf("got wrong result OTPState: expected: %v, actual: %v ", tt.result.OTPState, human.OTPState) } - if human.MfaInitSkipped.Round(1*time.Second) != tt.result.MfaInitSkipped.Round(1*time.Second) { - t.Errorf("got wrong result MfaInitSkipped: expected: %v, actual: %v ", tt.result.MfaInitSkipped.Round(1*time.Second), human.MfaInitSkipped.Round(1*time.Second)) + if human.MFAInitSkipped.Round(1*time.Second) != tt.result.MFAInitSkipped.Round(1*time.Second) { + t.Errorf("got wrong result MFAInitSkipped: expected: %v, actual: %v ", tt.result.MFAInitSkipped.Round(1*time.Second), human.MFAInitSkipped.Round(1*time.Second)) } if human.PasswordSet != tt.result.PasswordSet { t.Errorf("got wrong result PasswordSet: expected: %v, actual: %v ", tt.result.PasswordSet, human.PasswordSet) diff --git a/internal/user/repository/view/user_session_view.go b/internal/user/repository/view/user_session_view.go index 156a4d705f..930559af26 100644 --- a/internal/user/repository/view/user_session_view.go +++ b/internal/user/repository/view/user_session_view.go @@ -1,6 +1,7 @@ package view import ( + auth_model "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/view/repository" "github.com/jinzhu/gorm" @@ -58,6 +59,20 @@ func UserSessionsByAgentID(db *gorm.DB, table, agentID string) ([]*model.UserSes return userSessions, err } +func ActiveUserSessions(db *gorm.DB, table string) ([]*model.UserSessionView, error) { + userSessions := make([]*model.UserSessionView, 0) + activeQuery := &usr_model.UserSessionSearchQuery{ + Key: usr_model.UserSessionSearchKeyState, + Method: global_model.SearchMethodEquals, + Value: auth_model.UserSessionStateActive, + } + query := repository.PrepareSearchQuery(table, model.UserSessionSearchRequest{ + Queries: []*usr_model.UserSessionSearchQuery{activeQuery}, + }) + _, err := query(db, &userSessions) + return userSessions, err +} + func PutUserSession(db *gorm.DB, table string, session *model.UserSessionView) error { save := repository.PrepareSave(table) return save(db, session) diff --git a/internal/user/repository/view/user_view.go b/internal/user/repository/view/user_view.go index 9ae31f354c..30edc95903 100644 --- a/internal/user/repository/view/user_view.go +++ b/internal/user/repository/view/user_view.go @@ -142,15 +142,15 @@ func IsUserUnique(db *gorm.DB, table, userName, email string) (bool, error) { return user.UserName == "", nil } -func UserMfas(db *gorm.DB, table, userID string) ([]*usr_model.MultiFactor, error) { +func UserMFAs(db *gorm.DB, table, userID string) ([]*usr_model.MultiFactor, error) { user, err := UserByID(db, table, userID) if err != nil { return nil, err } - if user.OTPState == int32(usr_model.MfaStateUnspecified) { + if user.OTPState == int32(usr_model.MFAStateUnspecified) { return []*usr_model.MultiFactor{}, nil } - return []*usr_model.MultiFactor{{Type: usr_model.MfaTypeOTP, State: usr_model.MfaState(user.OTPState)}}, nil + return []*usr_model.MultiFactor{{Type: usr_model.MFATypeOTP, State: usr_model.MFAState(user.OTPState)}}, nil } func PutUsers(db *gorm.DB, table string, users ...*model.UserView) error { diff --git a/internal/view/model/view.go b/internal/view/model/view.go index 109a35e186..6f766b912b 100644 --- a/internal/view/model/view.go +++ b/internal/view/model/view.go @@ -5,8 +5,9 @@ import ( ) type View struct { - Database string - ViewName string - CurrentSequence uint64 - CurrentTimestamp time.Time + Database string + ViewName string + CurrentSequence uint64 + EventTimestamp time.Time + LastSuccessfulSpoolerRun time.Time } diff --git a/internal/view/repository/failed_events.go b/internal/view/repository/failed_events.go index 8c3551702b..404aa58d8d 100644 --- a/internal/view/repository/failed_events.go +++ b/internal/view/repository/failed_events.go @@ -84,7 +84,7 @@ func SaveFailedEvent(db *gorm.DB, table string, failedEvent *FailedEvent) error err := save(db, failedEvent) if err != nil { - return errors.ThrowInternal(err, "VIEW-5kOhP", "unable to updated failed events") + return errors.ThrowInternal(err, "VIEW-4F8us", "unable to updated failed events") } return nil } diff --git a/internal/view/repository/sequence.go b/internal/view/repository/sequence.go index 1491c39642..10023b2b66 100644 --- a/internal/view/repository/sequence.go +++ b/internal/view/repository/sequence.go @@ -9,9 +9,10 @@ import ( ) type CurrentSequence struct { - ViewName string `gorm:"column:view_name;primary_key"` - CurrentSequence uint64 `gorm:"column:current_sequence"` - CurrentTimestamp time.Time `gorm:"column:timestamp"` + ViewName string `gorm:"column:view_name;primary_key"` + CurrentSequence uint64 `gorm:"column:current_sequence"` + EventTimestamp time.Time `gorm:"column:event_timestamp"` + LastSuccessfulSpoolerRun time.Time `gorm:"column:last_successful_spooler_run"` } type SequenceSearchKey int32 @@ -35,16 +36,21 @@ func (key sequenceSearchKey) ToColumnName() string { func CurrentSequenceToModel(sequence *CurrentSequence) *model.View { dbView := strings.Split(sequence.ViewName, ".") return &model.View{ - Database: dbView[0], - ViewName: dbView[1], - CurrentSequence: sequence.CurrentSequence, - CurrentTimestamp: sequence.CurrentTimestamp, + Database: dbView[0], + ViewName: dbView[1], + CurrentSequence: sequence.CurrentSequence, + EventTimestamp: sequence.EventTimestamp, + LastSuccessfulSpoolerRun: sequence.LastSuccessfulSpoolerRun, } } -func SaveCurrentSequence(db *gorm.DB, table, viewName string, sequence uint64) error { +func SaveCurrentSequence(db *gorm.DB, table, viewName string, sequence uint64, eventTimestamp time.Time) error { + return UpdateCurrentSequence(db, table, &CurrentSequence{viewName, sequence, eventTimestamp, time.Now()}) +} + +func UpdateCurrentSequence(db *gorm.DB, table string, currentSequence *CurrentSequence) error { save := PrepareSave(table) - err := save(db, &CurrentSequence{viewName, sequence, time.Now()}) + err := save(db, currentSequence) if err != nil { return caos_errs.ThrowInternal(err, "VIEW-5kOhP", "unable to updated processed sequence") @@ -83,5 +89,5 @@ func ClearView(db *gorm.DB, truncateView, sequenceTable string) error { if err != nil { return err } - return SaveCurrentSequence(db, sequenceTable, truncateView, 0) + return SaveCurrentSequence(db, sequenceTable, truncateView, 0, time.Now()) } diff --git a/internal/webauthn/converter.go b/internal/webauthn/converter.go new file mode 100644 index 0000000000..efe8b8bb26 --- /dev/null +++ b/internal/webauthn/converter.go @@ -0,0 +1,80 @@ +package webauthn + +import ( + "github.com/caos/zitadel/internal/user/model" + "github.com/duo-labs/webauthn/protocol" + "github.com/duo-labs/webauthn/webauthn" +) + +func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credential { + creds := make([]webauthn.Credential, 0) + for _, webAuthN := range webAuthNs { + if webAuthN.State == model.MFAStateReady { + creds = append(creds, webauthn.Credential{ + ID: webAuthN.KeyID, + PublicKey: webAuthN.PublicKey, + AttestationType: webAuthN.AttestationType, + Authenticator: webauthn.Authenticator{ + AAGUID: webAuthN.AAGUID, + SignCount: webAuthN.SignCount, + }, + }) + } + } + return creds +} + +func WebAuthNToSessionData(webAuthN *model.WebAuthNToken) webauthn.SessionData { + return webauthn.SessionData{ + Challenge: webAuthN.Challenge, + UserID: []byte(webAuthN.AggregateID), + AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, + UserVerification: UserVerificationFromModel(webAuthN.UserVerification), + } +} + +func WebAuthNLoginToSessionData(webAuthN *model.WebAuthNLogin) webauthn.SessionData { + return webauthn.SessionData{ + Challenge: webAuthN.Challenge, + UserID: []byte(webAuthN.AggregateID), + AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, + UserVerification: UserVerificationFromModel(webAuthN.UserVerification), + } +} + +func UserVerificationToModel(verification protocol.UserVerificationRequirement) model.UserVerificationRequirement { + switch verification { + case protocol.VerificationRequired: + return model.UserVerificationRequirementRequired + case protocol.VerificationPreferred: + return model.UserVerificationRequirementPreferred + case protocol.VerificationDiscouraged: + return model.UserVerificationRequirementDiscouraged + default: + return model.UserVerificationRequirementUnspecified + } +} + +func UserVerificationFromModel(verification model.UserVerificationRequirement) protocol.UserVerificationRequirement { + switch verification { + case model.UserVerificationRequirementRequired: + return protocol.VerificationRequired + case model.UserVerificationRequirementPreferred: + return protocol.VerificationPreferred + case model.UserVerificationRequirementDiscouraged: + return protocol.VerificationDiscouraged + default: + return protocol.VerificationDiscouraged + } +} + +func AuthenticatorAttachmentFromModel(authType model.AuthenticatorAttachment) protocol.AuthenticatorAttachment { + switch authType { + case model.AuthenticatorAttachmentPlattform: + return protocol.Platform + case model.AuthenticatorAttachmentCrossPlattform: + return protocol.CrossPlatform + default: + return "" + } +} diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go new file mode 100644 index 0000000000..62af99ff38 --- /dev/null +++ b/internal/webauthn/webauthn.go @@ -0,0 +1,158 @@ +package webauthn + +import ( + "bytes" + "encoding/json" + + "github.com/duo-labs/webauthn/protocol" + "github.com/duo-labs/webauthn/webauthn" + + caos_errs "github.com/caos/zitadel/internal/errors" + usr_model "github.com/caos/zitadel/internal/user/model" +) + +type WebAuthN struct { + web *webauthn.WebAuthn +} + +func StartServer(displayName, id, origin string) (*WebAuthN, error) { + web, err := webauthn.New(&webauthn.Config{ + RPDisplayName: displayName, + RPID: id, + RPOrigin: origin, + }) + if err != nil { + return nil, err + } + return &WebAuthN{ + web: web, + }, err +} + +type webUser struct { + *usr_model.User + credentials []webauthn.Credential +} + +func (u *webUser) WebAuthnID() []byte { + return []byte(u.AggregateID) +} + +func (u *webUser) WebAuthnName() string { + return u.UserName +} + +func (u *webUser) WebAuthnDisplayName() string { + return u.DisplayName +} + +func (u *webUser) WebAuthnIcon() string { + return "" +} + +func (u *webUser) WebAuthnCredentials() []webauthn.Credential { + return u.credentials +} + +func (w *WebAuthN) BeginRegistration(user *usr_model.User, authType usr_model.AuthenticatorAttachment, userVerification usr_model.UserVerificationRequirement, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNToken, error) { + creds := WebAuthNsToCredentials(webAuthNs) + existing := make([]protocol.CredentialDescriptor, len(creds)) + for i, cred := range creds { + existing[i] = protocol.CredentialDescriptor{ + Type: protocol.PublicKeyCredentialType, + CredentialID: cred.ID, + } + } + credentialOptions, sessionData, err := w.web.BeginRegistration( + &webUser{ + User: user, + credentials: creds, + }, + webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ + UserVerification: UserVerificationFromModel(userVerification), + AuthenticatorAttachment: AuthenticatorAttachmentFromModel(authType), + }), + webauthn.WithConveyancePreference(protocol.PreferNoAttestation), + webauthn.WithExclusions(existing), + ) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-bM8sd", "Errors.User.WebAuthN.BeginRegisterFailed") + } + cred, err := json.Marshal(credentialOptions) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError") + } + return &usr_model.WebAuthNToken{ + Challenge: sessionData.Challenge, + CredentialCreationData: cred, + AllowedCredentialIDs: sessionData.AllowedCredentialIDs, + UserVerification: UserVerificationToModel(sessionData.UserVerification), + }, nil +} + +func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.WebAuthNToken, tokenName string, credData []byte) (*usr_model.WebAuthNToken, error) { + if webAuthN == nil { + return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound") + } + credentialData, err := protocol.ParseCredentialCreationResponseBody(bytes.NewReader(credData)) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential") + } + sessionData := WebAuthNToSessionData(webAuthN) + credential, err := w.web.CreateCredential( + &webUser{ + User: user, + }, + sessionData, credentialData) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-3Vb9s", "Errors.User.WebAuthN.CreateCredentialFailed") + } + + webAuthN.KeyID = credential.ID + webAuthN.PublicKey = credential.PublicKey + webAuthN.AttestationType = credential.AttestationType + webAuthN.AAGUID = credential.Authenticator.AAGUID + webAuthN.SignCount = credential.Authenticator.SignCount + webAuthN.WebAuthNTokenName = tokenName + return webAuthN, nil +} + +func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.UserVerificationRequirement, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNLogin, error) { + assertion, sessionData, err := w.web.BeginLogin(&webUser{ + User: user, + credentials: WebAuthNsToCredentials(webAuthNs), + }, webauthn.WithUserVerification(UserVerificationFromModel(userVerification))) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-4G8sw", "Errors.User.WebAuthN.BeginLoginFailed") + } + cred, err := json.Marshal(assertion) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError") + } + return &usr_model.WebAuthNLogin{ + Challenge: sessionData.Challenge, + CredentialAssertionData: cred, + AllowedCredentialIDs: sessionData.AllowedCredentialIDs, + UserVerification: userVerification, + }, nil +} + +func (w *WebAuthN) FinishLogin(user *usr_model.User, webAuthN *usr_model.WebAuthNLogin, credData []byte, webAuthNs ...*usr_model.WebAuthNToken) ([]byte, uint32, error) { + assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData)) + if err != nil { + return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed") + } + webUser := &webUser{ + User: user, + credentials: WebAuthNsToCredentials(webAuthNs), + } + credential, err := w.web.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData) + if err != nil { + return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed") + } + + if credential.Authenticator.CloneWarning { + return credential.ID, credential.Authenticator.SignCount, caos_errs.ThrowInternal(err, "WEBAU-4M90s", "Errors.User.WebAuthN.CloneWarning") + } + return credential.ID, credential.Authenticator.SignCount, nil +} diff --git a/migrations/cockroach/V1.24__current_sequence.sql b/migrations/cockroach/V1.24__current_sequence.sql new file mode 100644 index 0000000000..6d937598f9 --- /dev/null +++ b/migrations/cockroach/V1.24__current_sequence.sql @@ -0,0 +1,11 @@ +ALTER TABLE management.current_sequences ADD COLUMN last_successful_spooler_run TIMESTAMPTZ; +ALTER TABLE auth.current_sequences ADD COLUMN last_successful_spooler_run TIMESTAMPTZ; +ALTER TABLE authz.current_sequences ADD COLUMN last_successful_spooler_run TIMESTAMPTZ; +ALTER TABLE adminapi.current_sequences ADD COLUMN last_successful_spooler_run TIMESTAMPTZ; +ALTER TABLE notification.current_sequences ADD COLUMN last_successful_spooler_run TIMESTAMPTZ; + +ALTER TABLE management.current_sequences RENAME COLUMN timestamp TO event_timestamp; +ALTER TABLE auth.current_sequences RENAME COLUMN timestamp TO event_timestamp; +ALTER TABLE authz.current_sequences RENAME COLUMN timestamp TO event_timestamp; +ALTER TABLE adminapi.current_sequences RENAME COLUMN timestamp TO event_timestamp; +ALTER TABLE notification.current_sequences RENAME COLUMN timestamp TO event_timestamp; diff --git a/migrations/cockroach/V1.25__webauthn.sql b/migrations/cockroach/V1.25__webauthn.sql new file mode 100644 index 0000000000..67787c7c1f --- /dev/null +++ b/migrations/cockroach/V1.25__webauthn.sql @@ -0,0 +1,13 @@ +ALTER TABLE management.login_policies ADD COLUMN passwordless_type SMALLINT; +ALTER TABLE adminapi.login_policies ADD COLUMN passwordless_type SMALLINT; +ALTER TABLE auth.login_policies ADD COLUMN passwordless_type SMALLINT; + +ALTER TABLE management.users ADD COLUMN u2f_tokens BYTEA; +ALTER TABLE auth.users ADD COLUMN u2f_tokens BYTEA; +ALTER TABLE adminapi.users ADD COLUMN u2f_tokens BYTEA; + +ALTER TABLE management.users ADD COLUMN passwordless_tokens BYTEA; +ALTER TABLE auth.users ADD COLUMN passwordless_tokens BYTEA; +ALTER TABLE adminapi.users ADD COLUMN passwordless_tokens BYTEA; + +ALTER TABLE auth.user_sessions ADD COLUMN passwordless_verification TIMESTAMPTZ; diff --git a/pkg/grpc/admin/proto/admin.proto b/pkg/grpc/admin/proto/admin.proto index 3e0b685a0c..d0c44df3e6 100644 --- a/pkg/grpc/admin/proto/admin.proto +++ b/pkg/grpc/admin/proto/admin.proto @@ -853,7 +853,8 @@ message View { string database = 1; string view_name = 2; uint64 processed_sequence = 3; - google.protobuf.Timestamp view_timestamp = 4; + google.protobuf.Timestamp event_timestamp = 4; + google.protobuf.Timestamp last_successful_spooler_run = 5; } message IdpID { @@ -998,6 +999,7 @@ message DefaultLoginPolicy { google.protobuf.Timestamp creation_date = 4; google.protobuf.Timestamp change_date = 5; bool force_mfa = 6; + PasswordlessType passwordless_type = 7; } message DefaultLoginPolicyRequest { @@ -1005,6 +1007,12 @@ message DefaultLoginPolicyRequest { bool allow_register = 2; bool allow_external_idp = 3; bool force_mfa = 4; + PasswordlessType passwordless_type = 5; +} + +enum PasswordlessType { + PASSWORDLESSTYPE_NOT_ALLOWED = 0; + PASSWORDLESSTYPE_ALLOWED = 1; } message IdpProviderID { @@ -1018,6 +1026,7 @@ message DefaultLoginPolicyView { google.protobuf.Timestamp creation_date = 4; google.protobuf.Timestamp change_date = 5; bool force_mfa = 6; + PasswordlessType passwordless_type = 7; } message IdpProviderView { diff --git a/pkg/grpc/auth/mock/auth.proto.mock.go b/pkg/grpc/auth/mock/auth.proto.mock.go index 22f851fa4c..d7ca8c5e4a 100644 --- a/pkg/grpc/auth/mock/auth.proto.mock.go +++ b/pkg/grpc/auth/mock/auth.proto.mock.go @@ -56,6 +56,46 @@ func (mr *MockAuthServiceClientMockRecorder) AddMfaOTP(arg0, arg1 interface{}, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMfaOTP", reflect.TypeOf((*MockAuthServiceClient)(nil).AddMfaOTP), varargs...) } +// AddMyMfaU2F mocks base method +func (m *MockAuthServiceClient) AddMyMfaU2F(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*auth.WebAuthNResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddMyMfaU2F", varargs...) + ret0, _ := ret[0].(*auth.WebAuthNResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddMyMfaU2F indicates an expected call of AddMyMfaU2F +func (mr *MockAuthServiceClientMockRecorder) AddMyMfaU2F(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMyMfaU2F", reflect.TypeOf((*MockAuthServiceClient)(nil).AddMyMfaU2F), varargs...) +} + +// AddMyPasswordless mocks base method +func (m *MockAuthServiceClient) AddMyPasswordless(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*auth.WebAuthNResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddMyPasswordless", varargs...) + ret0, _ := ret[0].(*auth.WebAuthNResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddMyPasswordless indicates an expected call of AddMyPasswordless +func (mr *MockAuthServiceClientMockRecorder) AddMyPasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMyPasswordless", reflect.TypeOf((*MockAuthServiceClient)(nil).AddMyPasswordless), varargs...) +} + // ChangeMyPassword mocks base method func (m *MockAuthServiceClient) ChangeMyPassword(arg0 context.Context, arg1 *auth.PasswordChange, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { m.ctrl.T.Helper() @@ -416,6 +456,46 @@ func (mr *MockAuthServiceClientMockRecorder) RemoveMyExternalIDP(arg0, arg1 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMyExternalIDP", reflect.TypeOf((*MockAuthServiceClient)(nil).RemoveMyExternalIDP), varargs...) } +// RemoveMyMfaU2F mocks base method +func (m *MockAuthServiceClient) RemoveMyMfaU2F(arg0 context.Context, arg1 *auth.WebAuthNTokenID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RemoveMyMfaU2F", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RemoveMyMfaU2F indicates an expected call of RemoveMyMfaU2F +func (mr *MockAuthServiceClientMockRecorder) RemoveMyMfaU2F(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMyMfaU2F", reflect.TypeOf((*MockAuthServiceClient)(nil).RemoveMyMfaU2F), varargs...) +} + +// RemoveMyPasswordless mocks base method +func (m *MockAuthServiceClient) RemoveMyPasswordless(arg0 context.Context, arg1 *auth.WebAuthNTokenID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RemoveMyPasswordless", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RemoveMyPasswordless indicates an expected call of RemoveMyPasswordless +func (mr *MockAuthServiceClientMockRecorder) RemoveMyPasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMyPasswordless", reflect.TypeOf((*MockAuthServiceClient)(nil).RemoveMyPasswordless), varargs...) +} + // RemoveMyUserPhone mocks base method func (m *MockAuthServiceClient) RemoveMyUserPhone(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { m.ctrl.T.Helper() @@ -596,6 +676,46 @@ func (mr *MockAuthServiceClientMockRecorder) VerifyMfaOTP(arg0, arg1 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyMfaOTP", reflect.TypeOf((*MockAuthServiceClient)(nil).VerifyMfaOTP), varargs...) } +// VerifyMyMfaU2F mocks base method +func (m *MockAuthServiceClient) VerifyMyMfaU2F(arg0 context.Context, arg1 *auth.VerifyWebAuthN, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "VerifyMyMfaU2F", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyMyMfaU2F indicates an expected call of VerifyMyMfaU2F +func (mr *MockAuthServiceClientMockRecorder) VerifyMyMfaU2F(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyMyMfaU2F", reflect.TypeOf((*MockAuthServiceClient)(nil).VerifyMyMfaU2F), varargs...) +} + +// VerifyMyPasswordless mocks base method +func (m *MockAuthServiceClient) VerifyMyPasswordless(arg0 context.Context, arg1 *auth.VerifyWebAuthN, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "VerifyMyPasswordless", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyMyPasswordless indicates an expected call of VerifyMyPasswordless +func (mr *MockAuthServiceClientMockRecorder) VerifyMyPasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyMyPasswordless", reflect.TypeOf((*MockAuthServiceClient)(nil).VerifyMyPasswordless), varargs...) +} + // VerifyMyUserEmail mocks base method func (m *MockAuthServiceClient) VerifyMyUserEmail(arg0 context.Context, arg1 *auth.VerifyMyUserEmailRequest, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { m.ctrl.T.Helper() diff --git a/pkg/grpc/auth/proto/auth.proto b/pkg/grpc/auth/proto/auth.proto index 723a614f86..fccedd0b55 100644 --- a/pkg/grpc/auth/proto/auth.proto +++ b/pkg/grpc/auth/proto/auth.proto @@ -244,12 +244,12 @@ service AuthService { rpc GetMyPasswordComplexityPolicy(google.protobuf.Empty) returns (PasswordComplexityPolicy) { option (google.api.http) = { - get: "/policies/passwords/complexity" - }; + get: "/policies/passwords/complexity" + }; option (caos.zitadel.utils.v1.auth_option) = { - permission: "authenticated" - }; + permission: "authenticated" + }; } //ExternalIDP @@ -306,6 +306,68 @@ service AuthService { }; } + rpc AddMyMfaU2F(google.protobuf.Empty) returns (WebAuthNResponse) { + option (google.api.http) = { + post: "/users/me/mfas/u2f" + body: "*" + }; + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + + rpc VerifyMyMfaU2F(VerifyWebAuthN) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/users/me/mfas/u2f/_verify" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + + rpc RemoveMyMfaU2F(WebAuthNTokenID) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/users/me/mfas/u2f/{id}" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + + rpc AddMyPasswordless(google.protobuf.Empty) returns (WebAuthNResponse) { + option (google.api.http) = { + post: "/users/me/passwordless" + body: "*" + }; + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + + rpc VerifyMyPasswordless(VerifyWebAuthN) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/users/me/passwordless/_verify" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + + rpc RemoveMyPasswordless(WebAuthNTokenID) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/users/me/passwordless/{id}" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "authenticated" + }; + } + rpc SearchMyUserGrant(UserGrantSearchRequest) returns (UserGrantSearchResponse) { option (google.api.http) = { post: "/usergrants/me/_search" @@ -578,8 +640,8 @@ message PasswordChange { enum MfaType { MFATYPE_UNSPECIFIED = 0; - MFATYPE_SMS = 1; - MFATYPE_OTP = 2; + MFATYPE_OTP = 1; + MFATYPE_U2F = 2; } message VerifyMfaOtp { @@ -593,6 +655,7 @@ message MultiFactors { message MultiFactor { MfaType type = 1; MFAState state = 2; + string attribute = 3; } message MfaOtpResponse { @@ -602,6 +665,21 @@ message MfaOtpResponse { MFAState state = 4; } +message WebAuthNResponse { + string id = 1; + bytes public_key = 2; + MFAState state = 3; +} + +message VerifyWebAuthN { + bytes public_key_credential = 1; + string token_name = 2; +} + +message WebAuthNTokenID { + string id = 1; +} + enum MFAState { MFASTATE_UNSPECIFIED = 0; MFASTATE_NOT_READY = 1; @@ -691,7 +769,7 @@ enum SearchMethod { } message ChangesRequest { - uint64 limit= 1; + uint64 limit = 1; uint64 sequence_offset = 2; bool asc = 3; } diff --git a/pkg/grpc/management/proto/management.proto b/pkg/grpc/management/proto/management.proto index d4184d9eb4..94d5564652 100644 --- a/pkg/grpc/management/proto/management.proto +++ b/pkg/grpc/management/proto/management.proto @@ -2033,8 +2033,8 @@ message UserMultiFactor { enum MfaType { MFATYPE_UNSPECIFIED = 0; - MFATYPE_SMS = 1; - MFATYPE_OTP = 2; + MFATYPE_OTP = 1; + MFATYPE_U2F = 2; } enum MFAState { @@ -3064,6 +3064,7 @@ message LoginPolicy { google.protobuf.Timestamp creation_date = 4; google.protobuf.Timestamp change_date = 5; bool force_mfa = 6; + PasswordlessType passwordless_type = 7; } message LoginPolicyRequest { @@ -3071,6 +3072,12 @@ message LoginPolicyRequest { bool allow_register = 2; bool allow_external_idp = 3; bool force_mfa = 4; + PasswordlessType passwordless_type = 5; +} + +enum PasswordlessType { + PASSWORDLESSTYPE_NOT_ALLOWED = 0; + PASSWORDLESSTYPE_ALLOWED = 1; } message IdpProviderID { @@ -3095,6 +3102,7 @@ message LoginPolicyView { google.protobuf.Timestamp creation_date = 5; google.protobuf.Timestamp change_date = 6; bool force_mfa = 7; + PasswordlessType passwordless_type = 8; } message IdpProviderView { diff --git a/site/README.md b/site/README.md index 39a375934b..8516a6a1f7 100644 --- a/site/README.md +++ b/site/README.md @@ -5,14 +5,20 @@ The documentation is built according to the structure of a docs `folder`[Folder] ## Running locally -Set up the project: +You can simply run the static site by using the docker-compose command below. -```bash -npm i +```Bash +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f site/docker-compose.yml up --build ``` -Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000). +## Building locally + +You can simply run the static site by using the docker-compose command below. + +```Bash +DOCKER_BUILDKIT=1 docker build -f site/dockerfile . -t zitadel:docs -o docs +``` ## Honorable Mentions -This project was created with the help of some components from [svelte](https://github.com/sveltejs/svelte)([MIT](https://github.com/sveltejs/svelte/blob/master/LICENSE)) as well as [site-kit](https://github.com/sveltejs/site-kit)([MIT](https://github.com/sveltejs/site-kit/blob/master/LICENSE)). \ No newline at end of file +This project was created with the help of some components from [svelte](https://github.com/sveltejs/svelte)([MIT](https://github.com/sveltejs/svelte/blob/master/LICENSE)) as well as [site-kit](https://github.com/sveltejs/site-kit)([MIT](https://github.com/sveltejs/site-kit/blob/master/LICENSE)). diff --git a/site/docker-compose.yml b/site/docker-compose.yml new file mode 100644 index 0000000000..3da8edfc92 --- /dev/null +++ b/site/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + docs: + build: + context: .. + dockerfile: site/dockerfile + command: sh -c "npm run dev" + ports: + - 3000:3000 \ No newline at end of file diff --git a/site/dockerfile b/site/dockerfile new file mode 100644 index 0000000000..ff55563bee --- /dev/null +++ b/site/dockerfile @@ -0,0 +1,13 @@ +FROM node:15 as builder + +COPY site/ /site/ + +WORKDIR /site + +RUN npm install + +RUN npx sapper export --legacy + +FROM scratch as final + +COPY --from=builder /site/__sapper__/export . \ No newline at end of file diff --git a/site/docs/administrate/00-overview.en.md b/site/docs/administrate/00-overview.en.md index 74963982db..a7ed644984 100644 --- a/site/docs/administrate/00-overview.en.md +++ b/site/docs/administrate/00-overview.en.md @@ -40,4 +40,4 @@ title: Overview ### Concepts With ZITADEL there are some key concepts some should be aware of before using it to secure your applications and services. -You find these definitions in the "What is..." heading of each resource. +You find these definitions in the "What is/are..." heading of each resource. diff --git a/site/docs/administrate/01-console.en.md b/site/docs/administrate/01-console.en.md index a37072528c..f7a2aa54e5 100644 --- a/site/docs/administrate/01-console.en.md +++ b/site/docs/administrate/01-console.en.md @@ -4,7 +4,10 @@ title: Console ### What is Console -Console is the ZITADEL Graphical User Interface. +Console is the ZITADEL Graphical User Interface. + +ZITADEL Console can be reached at [console.zitadel.ch](https://console.zitadel.ch/). + For dedicated ZITADEL instances this URL might be different, but in most cases should be something like `https://console.YOURDOMAIN.TLD` #### ZITADEL Users @@ -31,7 +34,7 @@ Console is the ZITADEL Graphical User Interface. #### ZITADEL Organisation Owners -Users (**org owners**) who manage organisations do this also with Console. +Users who manage organisations (**organisation owners**) do this also with Console. - Organisation settings (policies, domains, idps) - Manage users diff --git a/site/docs/administrate/02-organisations.en.md b/site/docs/administrate/02-organisations.en.md index 15738d7d64..7c068e3544 100644 --- a/site/docs/administrate/02-organisations.en.md +++ b/site/docs/administrate/02-organisations.en.md @@ -1,48 +1,67 @@ --- -title: Organisations +title: Organizations --- -### What are organisations +### What are organizations -Organisations are comparable to tenants of a system or OU's (organisational units) if we speak of a directory based system. -ZITADEL is organised around the idea that multiple organisations share the same [System](administrate#What_is_meant_by_system) and that they can grant each other rights so self manage certain things. +Organizations are comparable to tenants of a system or OU's (organizational units) if we speak of a directory based system. +ZITADEL is organized around the idea that +* multiple organizations share the same [System](administrate#What_is_meant_by_system) +* these organizations can grant each other rights to self-manage certain things (eg, delegating roles) +* organizations are a vessels for [users](administrate#What_are_users) and [projects](administrate#What_are_projects) -#### Global organisation +#### Global organization -ZITADEL provides a global organisation for users who manage their accounts on their own. Think of this like the difference between a "Microsoft Live Login" vs. "AzureAD User" -or if you think of Google "Gmail" vs "Gsuite". +The global organization holds users that are not assigned to any other organization in the [System](administrate#What_is_meant_by_system). Thus ZITADEL provides a global organization for users who manage their accounts on their own. -### Create an organisation without existing login +
+ + Example + +Let's look at our example company `acme.ch`: Suppose ACME sells online-tickets for concert venues. ACME created an organization `iam` to manage their own enterprise users (employees) and projects to manage the provided services. They also created an organization `b2b-partner-1`, allowing the partner self-manage their access. A partner could be a concert venue, that can administrate the backend of the service (e.g. posting new concerts, setting up billing, ...), and you want to allow them to self-manage access of users (e.g. employees of the venue) to their backend. Lastly, the organization `global` holds all the b2c customers of `acme.ch` that registered to the service to buy concert tickets. +
-ZITADEL allows you to create a new organisation without a pre-existing user. For [ZITADEL.ch](https://zitadel.ch) you can create a org by visiting the [Register organisation](https://accounts.zitadel.ch/register/org) +### Create an organization without existing login + +ZITADEL allows you to create a new organization without a pre-existing user. For [ZITADEL.ch](https://zitadel.ch) you can create a org by visiting the [Register organization](https://accounts.zitadel.ch/register/org) > Screenshot here +
+ + Dedicated Instance + For dedicated ZITADEL instances this URL might be different, but in most cases should be something like https://accounts.YOURDOMAIN.TLD/register/org +
-### Create an organisation with existing login +### Create an organization with existing login -You can simply create a new organisation by visiting the [ZITADEL Console](https://console.zitadel.ch) and clicking "new organisation" in the upper left corner. +You can simply create a new organization by visiting the [ZITADEL Console](https://console.zitadel.ch) and clicking "new organization" in the upper left corner. > Screenshot here +
+ + Dedicated Instance + For dedicated ZITADEL instances this URL might be different, but in most cases should be something like `https://console.YOURDOMAIN.TLD` +
### Verify a domain name -Once you created your organisation you will receive a generated domain name from ZITADEL for your organisation. For example if you call your organisation `ACME` you will receive `acme.zitadel.ch` as name. Furthermore the users you create will be suffixed with this domain name. To improve the user experience you can verify a domain name which you control. If you control acme.ch you can verify the ownership by DNS or HTTP challenge. +Once you created your organization you will receive a generated domain name from ZITADEL for your organization. For example if you call your organization `ACME` you will receive `acme.zitadel.ch` as name. Furthermore the users you create will be suffixed with this domain name. To improve the user experience you can verify a domain name which you control. If you control acme.ch you can verify the ownership by DNS or HTTP challenge. After the domain is verified your users can use both domain names to log-in. The user "coyote" can now use "coyote@acme.zitadel.ch" and "coyote@acme.ch". -An organisation can have multiple domain names, but only one of it can be primary. The primary domain defines which login name ZITADEL displays to the user, and also what information gets asserted in access_tokens (preferred_username). +An organization can have multiple domain names, but only one of it can be primary. The primary domain defines which login name ZITADEL displays to the user, and also what information gets asserted in access_tokens (preferred_username). -Browse to your [organisation](administrate#Organisations) by visiting [https://console.zitadel.ch/org](https://console.zitadel.ch/org). +Browse to your [organization](administrate#Organizations) by visiting [https://console.zitadel.ch/org](https://console.zitadel.ch/org). -Add the domain to your [organisation](administrate#Organisations) by clicking the button **Add Domain**. +Add the domain to your [organization](administrate#Organizations) by clicking the button **Add Domain**. @@ -50,33 +69,33 @@ Input the domain in the input field and click **Add** To start the domain verification click the domain name and a dialog will appear, where you can choose between DNS or HTTP challenge methods. For example, create a TXT record with your DNS provider for the used domain and click verify. **ZITADEL** will then proceed an check your DNS. @@ -89,7 +108,7 @@ When the verification is successful you have the option to activate the domain b Organization Domain Verified -
Organisation verified
+
Organization verified
@@ -105,9 +124,11 @@ Congratulations your are done! You can check this by visiting [https://console.z
-> This only works when the [user](administrate#Users) is member of this [organisation](administrate#Organisations) +> This only works when the [user](administrate#Users) is member of this [organization](administrate#Organizations) -### Manage Organisation ZITADEL Roles +### Manage Organization ZITADEL Roles + +You can assign users [management roles](https://docs.zitadel.ch/administrate#ZITADEL_s_management_Roles) to your new organization. -### Audit organisation changes +### Audit organization changes -All changes to the organisation are displayed on the organisation menu within [ZITADEL Console](https://console.zitadel.ch/org) organisation menu. Located on the right hand side under "activity". +All changes to the organization are displayed on the organization menu within [ZITADEL Console](https://console.zitadel.ch/org) organization menu. Located on the right hand side under "activity". > Screenshot here diff --git a/site/docs/administrate/03-projects.en.md b/site/docs/administrate/03-projects.en.md index 4d67df795a..e2b663f695 100644 --- a/site/docs/administrate/03-projects.en.md +++ b/site/docs/administrate/03-projects.en.md @@ -6,28 +6,27 @@ title: Projects The idea of projects is to have a vessel for all components who are closely related to each other. In ZITADEL all clients located in the same project share their roles, grants and authorizations. -From an access management perspective you manage who has what role in the project and your application consumes this information. -A project belongs to exactly one organisation. -The attribute project role assertion defines, if the roles should be integrated in the tokens without sending corresponding scope (urn:zitadel:iam:org:project:role:{rolename}) -With the project role check you can define if a user should have a requested role to be able to logon. +From an access management perspective you manage who has what role in the project and your application consumes this information. A project belongs to exactly one [organisation](administrate#Organisations). + +The attribute `project role assertion` defines, if the roles should be integrated in the tokens without sending corresponding scope (`urn:zitadel:iam:org:project:role:{rolename}`) + +With the project role check you can define, if a user should have a requested role to be able to logon. **Clients** -Clients are described here [What are clients](administrate#What_are_clients) -Basically these are your applications who initiate the authorization flow. +These are your applications who initiate the authorization flow (see [What are clients](administrate#What_are_clients)). **Roles** -[Roles (or Project Roles)](administrate#Roles) is a means of managing users access rights for a certain project. -These [roles](administrate#Roles) are opaque for ZITADEL and have no weight in relation to each other. -So if a [user](administrate#Users) has two roles, admin and user in a certain project, the information will be treated additive. +[Roles (or Project Roles)](administrate#Roles) are a means of managing users access rights for a certain project. These [roles](administrate#Roles) are opaque for ZITADEL and have no weight in relation to each other. + +As example, if [user](administrate#Users) has two roles, `admin` and `user` in a certain project, the information will be treated additive. There is no meaning or hierarchy implied by these roles. **Grants** -With ZITADEL it is possible to give third parties (other organisations) the possibility to manage certain roles on their own. -To achieve this the owner of a project can grant (some could say delegate) certain roles or all roles to an organisation. -After granting that organisation it can manage on its own which user has what roles. -This feature is especially useful for service providers, because they are able to establish a great self-service culture for their business customers. +With ZITADEL it is possible to give third parties (other organisations) the possibility to manage certain roles on their own. As a service provider, you will find this feature useful, as it allows you to establish a self-service culture for your business customers. + +The owner of a project can grant (some would say "delegate") certain roles or all roles to another organisation. The target organization can then independently manage the assignment of their users to the role within the [granted project](administrate#Project_vs_granted_Project). **Authorizations** @@ -35,8 +34,9 @@ This feature is especially useful for service providers, because they are able t #### Project vs. granted Project -The simple difference of a project vs a granted project is that a project belongs to your organisation and the granted project belongs to a third party who did grant you some rights to manage certain roles of their project. -To make it more easier to differentiate, ZITADEL Console displays these both as separate menu in the project section. +A project belongs to your organisation. You can [grant certain roles](administrate#Grant_project_to_a_third_party) to another organization. A granted project, on the other hand, belongs to a third party, granting you some rights to manage certain roles of their project. + +To make it more easier to differentiate ZITADEL Console displays these both as separate menu in the project section. ### Manage a project diff --git a/site/docs/administrate/04-clients.en.md b/site/docs/administrate/04-clients.en.md index d8ac5003b1..4fdc436b29 100644 --- a/site/docs/administrate/04-clients.en.md +++ b/site/docs/administrate/04-clients.en.md @@ -4,9 +4,17 @@ title: Clients ### What are clients -Clients are applications who share the same security context and interface with an "authorization server". +Clients are applications that share the same security context and interface with an "authorization server" (issuer of access tokens). + For example you could have a software project existing out of a web app and a mobile app, both of these applications might consume the same roles because the end user might use both of them. +Typical types of applications are: +* Web +* User Agent (Single-Page-Application) +* Native + +Check out our [Integration Guide](integrate#Overview) for more information. + ### Manage clients Clients might use different protocols for integrating with an IAM. With ZITADEL it is possible to use OpenID Connect 1.0 / OAuth 2.0. In the future SAML 2.0 support is planned as well. @@ -71,4 +79,4 @@ When the wizard is complete, the clients configuration will be displayed and you
Client Wizard Complete
-
\ No newline at end of file +
diff --git a/site/docs/administrate/05-roles.en.md b/site/docs/administrate/05-roles.en.md index 82028b3311..4bd418446e 100644 --- a/site/docs/administrate/05-roles.en.md +++ b/site/docs/administrate/05-roles.en.md @@ -4,7 +4,7 @@ title: Roles ### What are Roles -With **roles** **ZITADEL** lets [projects](administrate#projects) define there **role based access control**. +**ZITADEL** lets [projects](administrate#projects) define their **role based access control**. **Roles** can be consumed by the [clients](administrate#clients) which exist within a specific [project](administrate#projects). @@ -24,18 +24,18 @@ Each **role** consist of three fields. ### Granting Roles -To give someone (or somewhat) access to a [projects](administrate#projects) resources and services **ZITADEL** provides to processes. **Roles** can be either granted to [users](administrate#Users) org to [organisations](administrate#Organisations). +To give someone (or somewhat) access to a [project's](administrate#projects) resources and services **ZITADEL** provides two processes: **Roles** can either be granted to [users](administrate#Users) or to [organisations](administrate#Organisations). #### Grant Roles to Organisations -The possibility to grant **roles** to an [organisation](administrate#Organisations) is intended as "delegation" so that a [org](administrate#Organisations) can on their own grant access to [users](administrate#Users). +The possibility to grant **roles** to an [organisation](administrate#Organisations) is intended as "delegation" so that a [organisation](administrate#Organisations) can on their own grant access to [users](administrate#Users). -For example a **service provider** could grant the **roles** user, and manager to an [org](administrate#Organisations) as soon as they purchases his service. This can be automated by utilising a [service user](administrate#Manage_Service_Users) in the **service providers** business process. +For example a **service provider** could grant the **roles** `user`, and `manager` to an [organisation](administrate#Organisations) as soon as they purchases his service. This can be automated by utilising a [service user](administrate#Manage_Service_Users) in the **service providers** business process. > Screenshot here #### Grant Roles to Users -By granting **roles** to [users](administrate#Users), be it [humans or machines](administrate#Human_vs_Service_Users), this [user](administrate#Users) receives the authorization to access resources from a service. +By granting **roles** to [users](administrate#Users), be it [humans or machines](administrate#Human_vs_Service_Users), this [user](administrate#Users) receives the authorization to access a project's resources. > Screenshot here diff --git a/site/docs/administrate/06-users.en.md b/site/docs/administrate/06-users.en.md index d8ea3772ba..c1ca42f7fe 100644 --- a/site/docs/administrate/06-users.en.md +++ b/site/docs/administrate/06-users.en.md @@ -4,25 +4,27 @@ title: Users ### What are users -In **ZITADEL** there are different [users](administrate#Users). Some belong to dedicated [organisations](administrate#Organisations) other belong to the global [organisations](administrate#Organisations). Some of them are human [users](administrate#Users) others are machines. +In **ZITADEL** there are different [users](administrate#Users). Some belong to dedicated [organisations](administrate#Organisations) other belong to the [global organisation](administrate#Global_organisation). Some of them are human [users](administrate#Users) others are machines. Nonetheless we treat them all the same in regard to [roles](administrate#Roles) management and audit trail. #### Human vs. Service Users -The major difference between human vs. machine [users](administrate#Users) is the type of credentials who can be used. -With machine [users](administrate#Users) there is only a non interactive logon process possible. As such we utilize “JWT as Authorization Grant”. +The major difference between human vs. machine [users](administrate#Users) is the type of credentials that can be used: With machine [users](administrate#Users) there is only a non-interactive logon process possible. As such we utilize “JWT as Authorization Grant”. > TODO Link to “JWT as Authorization Grant” explanation. ### How ZITADEL handles usernames -**ZITADEL** is built around the concept of [organisations](administrate#Organisations). Each [organisation](administrate#Organisations) has its own pool of usernames which include human and service [users](administrate#Users). -For example a [user](administrate#Users) with the username `road.runner` can only exist once the [organisation](administrate#Organisations) `ACME`. **ZITADEL** will automatically generate a "logonname" for each [user](administrate#Users) consisting of `{username}@{domainname}.{zitadeldomain}`. Without verifying the domain name this would result in the logonname `road.runner@acme.zitadel.ch`. If you use a dedicated **ZITADEL** replace `zitadel.ch` with your domain name. +**ZITADEL** is built around the concept of [organisations](administrate#Organisations). Each [organisation](administrate#Organisations) has its own pool of usernames which includes human and service [users](administrate#Users). -If someone verifies a domain name within the organisation **ZITADEL** will generate additional logonames for each [user](administrate#Users) with that domain. For example if the domain is `acme.ch` the resulting logonname would be `road.runner@acme.ch` and as well the generated one `road.runner@acme.zitadel.ch`. +For example a [user](administrate#Users) with the username `road.runner` can only exist once in the [organisation](administrate#Organisations) `ACME`. **ZITADEL** will automatically generate a "logonname" for each [user](administrate#Users) consisting of `{username}@{domainname}.{zitadeldomain}`. Without [verifying the domain name](administrate#Verify_a_domain_name) this would result in the logonname `road.runner@acme.zitadel.ch`. -> Domain verification also removes the logonname from all [users](administrate#Users who might have used this combination in the global [organisation](administrate#Organisations). -> Relating to example with `acme.ch` if a user in the global [organisation](administrate#Organisations), let's call him `coyote` used `coyote@acme.ch` this logonname will be replaced with `coyote@randomvalue.tld` +> If you use a dedicated instance **ZITADEL** replace `zitadel.ch` with your domain name. + +If someone [verifies a domain name](administrate#Verify_a_domain_name) within the organisation, **ZITADEL** will generate additional logonames for each [user](administrate#Users) with the verified domain. For example if the domain is `acme.ch` the resulting logonname would be `road.runner@acme.ch` in addition to the already generated `road.runner@acme.zitadel.ch`. + +> Domain verification also removes the logonname from all [users](administrate#Users), who might have used this combination in the [global organisation](administrate#Global_organisation). +> Relating to example with `acme.ch` if a user in the [global organisation](administrate#Global_organisation), let's call him `coyote`, used `coyote@acme.ch` this logonname will be replaced with `coyote@randomvalue.tld` > **ZITADEL** notifies the user about this change ### Manage Users diff --git a/site/docs/administrate/07-policies.en.md b/site/docs/administrate/07-policies.en.md index 22120393dd..1fe0de8fa4 100644 --- a/site/docs/administrate/07-policies.en.md +++ b/site/docs/administrate/07-policies.en.md @@ -7,11 +7,13 @@ title: Policies Policies are a means of enforcing certain behaviour of ZITADEL. ZITADEL defines a default policy on the system level. However an organisation owner can change these aspects within his own organisation. +### Available policies + Below is a list of available policies -### Password complexity +#### Password complexity -This policy enforces passwords of users within the org. to be compliant. +This policy enforces passwords of users within the organization to be compliant. - min length - has number @@ -21,17 +23,18 @@ This policy enforces passwords of users within the org. to be compliant. > Screenshot here -### IAM Access Preference +#### IAM Access Preference -This policy enforces, when set to true, that usernames are suffixed with the organisations domain. -Under normal operation this policy is only false on the `global` org. so that users can choose their email as their username. -Only available for the `IAM Administrator` +If enabled, this policy enforces that usernames are suffixed with the organisations domain. +Under normal operation this policy is only false on the `global` organisation, so that users can choose their email as their username. + +Only available for the [IAM Administrator](administrate#ZITADEL_Administrators). > Screenshot here -### Login Options +#### Login Options -With this policy it is possible to define what options a user sees in the login process. +With this policy it is possible to define what options a user sees in the login process: - Username Password allowed - Self Register allowed @@ -40,7 +43,7 @@ With this policy it is possible to define what options a user sees in the login > Screenshot here -### Audit policy changes +#### Audit policy changes > Screenshot here diff --git a/site/docs/administrate/08-providers.en.md b/site/docs/administrate/08-providers.en.md index e2a7a8eb4f..f2729092bd 100644 --- a/site/docs/administrate/08-providers.en.md +++ b/site/docs/administrate/08-providers.en.md @@ -9,7 +9,7 @@ Normally federation uses protocols like [OpenID Connect 1.0](https://openid.net/ Some examples include: -#### Social Providers +**Social Providers** - Google Account - Microsoft Live Account @@ -18,13 +18,13 @@ Some examples include: - GitLab - ... -#### Enterprise Providers** +**Enterprise Providers** - Azure AD Tenant - Gsuite hosted domain - ... -### Generic +**Generic** - ADFS - ADDS @@ -33,8 +33,13 @@ Some examples include: ### What is Identity Brokering -ZITADEL supports the usage as identity broker, by linking multiple external idps into one user. -With identity brokering the client which relies on ZITADEL does not need to care about the linking of identity. +ZITADEL supports the usage as identity broker, by linking multiple external IDPs into one user. +With identity brokering the client, that relies on ZITADEL, doesn't need to care about the linking of identity. + +
+ Example + tbd. +
### Manage Identity Providers diff --git a/site/docs/administrate/09-authorisations.de.md b/site/docs/administrate/09-authorizations.de.md similarity index 78% rename from site/docs/administrate/09-authorisations.de.md rename to site/docs/administrate/09-authorizations.de.md index 89789830b3..42e58c92a8 100644 --- a/site/docs/administrate/09-authorisations.de.md +++ b/site/docs/administrate/09-authorizations.de.md @@ -1,5 +1,5 @@ --- -title: Autorisierungen +title: Authorizations --- > This Language is not yet translated. Please consult the English version. diff --git a/site/docs/administrate/09-authorisations.en.md b/site/docs/administrate/09-authorizations.en.md similarity index 91% rename from site/docs/administrate/09-authorisations.en.md rename to site/docs/administrate/09-authorizations.en.md index 6c9a3b2abc..243cc65466 100644 --- a/site/docs/administrate/09-authorisations.en.md +++ b/site/docs/administrate/09-authorizations.en.md @@ -1,12 +1,12 @@ --- -title: Authorisations +title: Authorizations --- -### What are Authorisations +### What are Authorizations -**ZITADEL** thinks of authorisations as resource who clearly defines which user has what roles. Authorisations are also called "user grant". +**ZITADEL** thinks of authorizations as resource who clearly defines which user has what roles. Authorizations are also called "user grant". -### Manage Authorisations +### Manage Authorizations You can grant Roles directly on a project. Or, if the user is in your organisation, by apply in the roles to the user directly. Additionaly you can use the authorization menu item to search for a user and project. diff --git a/site/docs/administrate/09-management-roles.de.md b/site/docs/administrate/09-management-roles.de.md index 42e58c92a8..95481566c9 100644 --- a/site/docs/administrate/09-management-roles.de.md +++ b/site/docs/administrate/09-management-roles.de.md @@ -1,5 +1,5 @@ --- -title: Authorizations +title: Management Rollen --- > This Language is not yet translated. Please consult the English version. diff --git a/site/docs/administrate/09-management-roles.en.md b/site/docs/administrate/09-management-roles.en.md index 8eaf1727d1..fca364a7b7 100644 --- a/site/docs/administrate/09-management-roles.en.md +++ b/site/docs/administrate/09-management-roles.en.md @@ -1,23 +1,23 @@ --- -title: Authorizations +title: Management roles --- -### ZITADEL's management Roles +### ZITADEL's management roles -ZITADEL's own role model is built around the IAM resource. The roles have some hierarchies to them. For example a IAM_OWNER can view and edit every resource of the system. ORG_OWNERS can only manage their resources included within their organisation. This includes projects, clients, users, and so on. +ZITADEL's own role model is built around the IAM resource. The roles have some hierarchies to them. For example a IAM_OWNER can view and edit every resource of the system. ORG_OWNERS can only manage their resources included within their organization. This includes projects, clients, users, and so on. -#### How to give a user ZITADEL Roles +#### How to give a user ZITADEL roles > Screenshots -##### System Roles +##### System roles IAM_OWNER IAM_OWNER_VIEWER -##### Organisation Roles +##### Organisation roles ORG_OWNER @@ -29,7 +29,7 @@ ORG_PROJECT_PERMISSION_EDITOR ORG_PROJECT_CREATOR -##### Owned Project Roles +##### Owned Project roles PROJECT_OWNER @@ -39,12 +39,12 @@ PROJECT_OWNER_GLOBAL PROJECT_OWNER_VIEWER_GLOBAL -##### Granted Project Roles +##### Granted Project roles PROJECT_GRANT_OWNER PROJECT_GRANT_OWNER_VIEWER -##### Project Roles Management +##### Project roles management > Explain Project Authorization diff --git a/site/docs/administrate/70-zitadelroles.en.md b/site/docs/administrate/70-zitadelroles.en.md index 82c6e065ba..0f99bda740 100644 --- a/site/docs/administrate/70-zitadelroles.en.md +++ b/site/docs/administrate/70-zitadelroles.en.md @@ -42,7 +42,7 @@ PROJECT_GRANT_OWNER_VIEWER ### Manage ZITADEL Roles -You can grant ZITADEL Roles directly on a resource like organisation or project. Or, if the user is in your organisation, by applying in the roles to the user directly. +You can grant ZITADEL Roles directly on a resource like organisation or project. Or, if the user is in your organisation, by applying the roles to the user directly: - [Manage Organisation ZITADEL Roles](administrate#Manage_Organisation_ZITADEL_Roles) - [Manage Project ZITADEL Roles](administrate#Manage_Organisation_ZITADEL_Roles) diff --git a/site/docs/documentation/03-openidoauth.en.md b/site/docs/documentation/03-openidoauth.en.md index 4cd14dff10..c001fbe567 100644 --- a/site/docs/documentation/03-openidoauth.en.md +++ b/site/docs/documentation/03-openidoauth.en.md @@ -8,12 +8,12 @@ This chapter documents the [OpenID Connect 1.0](https://openid.net/connect/) and Under normal circumstances **ZITADEL** need four domain names to operate properly. -| Domain Name | Example | Description | -|:------------|:--------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| issuer | issuer.zitadel.ch | Provides the [OpenID Connect 1.0 Discovery Endpoint](#openid-connect-10-discovery) | -| api | api.zitadel.ch | All ZITADEL API's are located under this domain see [API explanation](develop#APIs) for details | -| login | accounts.zitadel.ch | The accounts.* page provides server renderer pages like login and register and as well the authorization_endpoint for OpenID Connect | -| console | console.zitadel.ch | With the console.* domain we serve the assets for the management gui | +| Domain Name | Example | Description | +|:------------|:----------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| issuer | `issuer.zitadel.ch` | Provides the [OpenID Connect 1.0 Discovery Endpoint](#openid-connect-10-discovery) | +| api | `api.zitadel.ch` | All ZITADEL API's are located under this domain see [API explanation](develop#APIs) for details | +| login | `accounts.zitadel.ch` | The accounts.* page provides server renderer pages like login and register and as well the authorization_endpoint for OpenID Connect | +| console | `console.zitadel.ch` | With the console.* domain we serve the assets for the management gui | #### OpenID Connect 1.0 Discovery @@ -50,36 +50,106 @@ For example with [zitadel.ch](zitadel.ch) this would be the domain [issuer.zitad #### OAuth 2.0 Metadata -**ZITADEL** does not provide a OAuth 2.0 Metadata endpoint but instead provides a [OpenID Connect Discovery Endpoint](#openid-connect-10-discovery). +**ZITADEL** does not yet provide a OAuth 2.0 Metadata endpoint but instead provides a [OpenID Connect Discovery Endpoint](#openid-connect-10-discovery). ### Scopes -#### How scopes work +ZITADEL supports the usage of scopes as way of requesting information from the IAM and also instruct ZITADEL to do certain operations. -> TODO describe +#### Standard Scopes + +| Scopes | Example | Description | +|:--------|:----------|------------------------------------------------------| +| openid | `openid` | When using openid connect this is a mandatory scope | +| profile | `profile` | Optional scope to request the profile of the subject | +| email | `email` | Optional scope to request the email of the subject | +| address | `address` | Optional scope to request the address of the subject | + +#### Custom Scopes + +> This feature is not yet released #### Reserved Scopes In addition to the standard compliant scopes we utilize the following scopes. -| Scope | Description | Example | -|:------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------| -| urn:zitadel:iam:org:project:role:{rolename} | By using this scope a [client](administrate#clients) can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all [roles](administrate#Roles) to be asserted from the [project](administrate#projects) a [client](administrate#clients) belongs to. See details [here](administrate#RBAC_Settings) | urn:zitadel:iam:org:project:role:user | -| urn:zitadel:iam:org:domain:primary:{domainname} | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organisation. If the organisation does not exist a failure is displayed | urn:zitadel:iam:org:domain:primary:acme.ch | -| urn:zitadel:iam:role:{rolename} | | | -| urn:zitadel:iam:org:project:id:{projectid}:aud | By adding this scope, the requested projectid will be added to the audience of the access and id token | ZITADEL Project: urn:zitadel:iam:org:project:id:69234237810729019:aud | +| Scopes | Example | Description | +|:------------------------------------------------|:-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| urn:zitadel:iam:org:project:role:{rolename} | `urn:zitadel:iam:org:project:role:user` | By using this scope a [client](administrate#clients) can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all [roles](administrate#Roles) to be asserted from the [project](administrate#projects) a [client](administrate#clients) belongs to. See details [here](administrate#RBAC_Settings) | +| urn:zitadel:iam:org:domain:primary:{domainname} | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed | +| urn:zitadel:iam:role:{rolename} | | | +| urn:zitadel:iam:org:project:id:{projectid}:aud | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token | + +> If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request ### Claims -> TODO describe +ZITADEL asserts claims on different places according to the corresponding specifications or project and clients settings. +Please check below the matrix for an overview where which scope is asserted. + +| Claims | Userinfo | ID Token | Access Token | +|:------------------------------------------------|:-------------------|----------------------------------------|------------------------------------------| +| acr | Yes | Yes | No | +| address | Yes when requested | Yes only when response type `id_token` | No | +| amr | Yes | Yes | No | +| aud | No | Yes | Yes when JWT | +| auth_time | Yes | Yes | No | +| azp | No | Yes | Yes when JWT | +| email | Yes when requested | Yes only when response type `id_token` | No | +| email_verified | Yes when requested | Yes only when response type `id_token` | No | +| exp | No | Yes | Yes when JWT | +| family_name | Yes when requested | Yes when requested | No | +| gender | Yes when requested | Yes when requested | No | +| given_name | Yes when requested | Yes when requested | No | +| iat | No | Yes | Yes when JWT | +| iss | No | Yes | Yes when JWT | +| locale | Yes when requested | Yes when requested | No | +| name | Yes when requested | Yes when requested | No | +| nonce | No | Yes | No | +| phone | Yes when requested | Yes only when response type `id_token` | No | +| preferred_username | Yes when requested | Yes | No | +| sub | Yes | Yes | Yes when JWT | +| urn:zitadel:iam:org:domain:primary:{domainname} | Yes when requested | Yes when requested | Yes when JWT and requested | +| urn:zitadel:iam:org:project:roles:{rolename} | Yes when requested | Yes when requested or configured | Yes when JWT and requested or configured | + +#### Standard Claims + +| Claims | Example | Description | +|:-------------------|:-----------------------------------------|-----------------------------------------------------------------------------------------------| +| acr | TBA | TBA | +| address | `Teufener Strasse 19, 9000 St. Gallen` | TBA | +| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) | +| aud | `69234237810729019` | By default all client id's and the project id is included | +| auth_time | `1311280969` | Unix time of the authentication | +| azp | `69234237810729234` | Client id of the client who requested the token | +| email | `road.runner@acme.ch` | Email Address of the subject | +| email_verified | `true` | Boolean if the email was verified by ZITADEL | +| exp | `1311281970` | Time the token expires as unix time | +| family_name | `Runner` | The subjects family name | +| gender | `other` | Gender of the subject | +| given_name | `Road` | Given name of the subject | +| iat | `1311280970` | Issued at time of the token as unix time | +| iss | `https://issuer.zitadel.ch` | Issuing domain of a token | +| locale | `en` | Language from the subject | +| name | `Road Runner` | The subjects full name | +| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client | +| phone | `+41 79 XXX XX XX` | Phone number provided by the user | +| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` | +| sub | `77776025198584418` | Subject ID of the user | + +#### Custom Claims + +> This feature is not yet released #### Reserved Claims -| Claims | Description | Example | -|:------------------------------------------------|:------------|----------------------------------------------------------------------------------| -| urn:zitadel:iam:org:domain:primary:{domainname} | | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | -| urn:zitadel:iam:org:project:roles:{rolename} | | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | -| urn:zitadel:iam:roles:{rolename} | | | +ZITADEL reserves some claims to assert certain data. + +| Claims | Example | Description | +|:------------------------------------------------|:-----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| urn:zitadel:iam:org:domain:primary:{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. | +| urn:zitadel:iam:org:project:roles:{rolename} | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role. | +| urn:zitadel:iam:roles:{rolename} | TBA | TBA | ### Grant Types @@ -89,12 +159,12 @@ For a list of supported or unsupported `Grant Types` please have a look at the t |:------------------------------------------------------|:--------------------| | Authorization Code | yes | | Authorization Code with PKCE | yes | -| Implicit | yes | -| Resource Owner Password Credentials | no | | Client Credentials | yes | | Device Authorization | under consideration | -| Refresh Token | work in progress | +| Implicit | yes | | JSON Web Token (JWT) Profile | partially | +| Refresh Token | work in progress | +| Resource Owner Password Credentials | no | | Security Assertion Markup Language (SAML) 2.0 Profile | no | | Token Exchange | work in progress | @@ -122,6 +192,82 @@ For a list of supported or unsupported `Grant Types` please have a look at the t **Link to spec.** [JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://tools.ietf.org/html/rfc7523) +##### Using JWTs as Authorization Grants + +Our service user work with the JWT profile to authenticate them against ZITADEL. + +1. Create or use an existing service user +2. Create a new key and download it +3. Generate a JWT with the structure below and sing it with the downloaded key +4. Send the JWT Base64 encoded to ZITADEL's token endpoint +5. Use the received access token + +--- + +Key JSON + +| Key | Example | Description | +|:-------|:--------------------------------------------------------------------|:-------------------------------------------------------------------| +| type | `"serviceaccount"` | The type of account, right now only serviceaccount is valid | +| keyId | `"81693565968772648"` | This is unique ID of the key | +| key | `"-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"` | The private key generated by ZITADEL, this can not be regenerated! | +| userId | `78366401571647008` | The service users ID, this is the same as the subject from tokens | + +```JSON +{ + "type": "serviceaccount", + "keyId": "81693565968772648", + "key": "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----", + "userId": "78366401571647008" +} +``` + +--- + +JWT + +| Claim | Example | Description | +|:------|:------------------------------|:---------------------------------------------------------------------------------| +| aud | `"https://issuer.zitadel.ch"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | +| exp | `1605183582` | Unix timestamp of the expiry, MUST NOT be longer than 1h | +| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT | +| iss | `"http://localhost:50003"` | String which represents the requesting party | +| sub | `"77479219772321307"` | The subject ID of the service user, normally the `userId` from the json key file | + +```JSON +{ + "iss": "http://localhost:50003", + "sub": "77479219772321307", + "aud": "https://issuer.zitadel.ch", + "exp": 1605183582, + "iat": 1605179982 +} +``` + +--- + +Access Token Request + +| Parameter | Example | Description | +|:-------------|:----------------------------------------------------------------------------|:----------------------------------------------| +| Content-Type | `application/x-www-form-urlencoded` | | +| grant_type | `urn:ietf:params:oauth:grant-type:jwt-bearer` | Using JWTs as Authorization Grants | +| assertion | `eyJhbGciOiJSUzI1Ni...` | The base64 encoded JWT created above | +| scope | `openid profile email urn:zitadel:iam:org:project:id:69234237810729019:aud` | Scopes you would like to request from ZITADEL | + +```BASH +curl --request POST \ + --url https://api.zitadel.ch/oauth/v2/token \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ + --data assertion=eyJhbGciOiJSUzI1Ni... + --data scope=openid profile email address +``` + +##### Using JWTs for Client Authentication + +> Not yet supported + #### Token Exchange **Link to spec.** [OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693) diff --git a/site/docs/integrate/01-singlepageapp.en.md b/site/docs/integrate/01-singlepageapp.en.md index e4c1a6eea8..4617f720a6 100644 --- a/site/docs/integrate/01-singlepageapp.en.md +++ b/site/docs/integrate/01-singlepageapp.en.md @@ -15,8 +15,6 @@ This flow has great support with most modern languages and frameworks and is the #### Typescript Authentication Example ---- - If you use a framework like Angular, Vue, React, ... you can use this code snippet here to integrate **ZITADEL** into you application Library used for this example [https://github.com/IdentityModel/oidc-client-js](https://github.com/IdentityModel/oidc-client-js) @@ -61,6 +59,4 @@ export default class AuthService { }); } } -``` - ---- +``` \ No newline at end of file diff --git a/site/docs/integrate/05-proxy.en.md b/site/docs/integrate/05-proxy.en.md index 727825c401..d14e504eea 100644 --- a/site/docs/integrate/05-proxy.en.md +++ b/site/docs/integrate/05-proxy.en.md @@ -94,7 +94,7 @@ spec: ```toml provider = "oidc" user_id_claim = "sub" #uses the subject as ID instead of the email -provider_display_name "ZITADEL" +provider_display_name = "ZITADEL" redirect_url = "http://127.0.0.1:4180/oauth2/callback" oidc_issuer_url = "https://issuer.zitadel.ch" upstreams = [ diff --git a/site/docs/start/00-quick-start.en.md b/site/docs/start/00-quick-start.en.md index 6f2a15e85f..0dc659a15d 100644 --- a/site/docs/start/00-quick-start.en.md +++ b/site/docs/start/00-quick-start.en.md @@ -5,12 +5,16 @@ description: A quick-start reference for the impatient reader. > All documentations are under active work and subject to change soon! -### Try ZITADEL +### Trying out ZITADEL -You can either use [ZITADEL.ch](https://zitadel.ch) or deploy a dedicated **ZITADEL** instance. +You can either use our cloud-instance [ZITADEL.ch](https://zitadel.ch) or deploy a dedicated **ZITADEL** instance. To get started, we recommend you try out our free tier on [ZITADEL.ch](https://zitadel.ch). ### Use ZITADEL.ch +To use ZITADEL in your project you need to: +1. Set up a new [client](administrate#What_are_clients) and [users](administrate#What_are_users) in our [Console](administrate#What_is_Console); +2. Configure your project to use ZITADEL. + To register your free [organisation](administrate#Organisations), visit this link [register organisation](https://accounts.zitadel.ch/register/org). After accepting the TOS and filling out all the required fields you will receive an email with further instructions. diff --git a/site/package.json b/site/package.json index 39e4dc0efa..62887a9457 100644 --- a/site/package.json +++ b/site/package.json @@ -9,8 +9,8 @@ "start": "node __sapper__/build", "cy:run": "cypress run", "cy:open": "cypress open", - "test": "run-p --race dev cy:run", - "imageoptim": "imageoptim --imagealpha 'static/img/*.png'" + "test": "run-p --race dev cy:run", + "imageoptim": "imageoptim --imagealpha 'static/img/*.png'" }, "dependencies": { "@polka/send": "^0.4.0", diff --git a/site/src/components/Docs.svelte b/site/src/components/Docs.svelte index d903ddc74c..e1b6bf5fb6 100644 --- a/site/src/components/Docs.svelte +++ b/site/src/components/Docs.svelte @@ -84,6 +84,21 @@