diff --git a/charts/cosmo/templates/dashboard/dashboard.yaml b/charts/cosmo/templates/dashboard/dashboard.yaml index 95067ef3..743b5466 100644 --- a/charts/cosmo/templates/dashboard/dashboard.yaml +++ b/charts/cosmo/templates/dashboard/dashboard.yaml @@ -86,6 +86,7 @@ spec: - --cookie-hashkey=$(COOKIE_HASHKEY) - --cookie-blockkey=$(COOKIE_BLOCKKEY) - --cookie-session-name=$(COOKIE_SESSION_NAME) + - --signin-url=$(SIGNIN_URL) {{- if ne .Values.dashboard.logging.level "info" }} - --zap-devel=true {{- end }} diff --git a/charts/cosmo/templates/dashboard/ingressroute.yaml b/charts/cosmo/templates/dashboard/ingressroute.yaml index 85004cfb..b37f7553 100644 --- a/charts/cosmo/templates/dashboard/ingressroute.yaml +++ b/charts/cosmo/templates/dashboard/ingressroute.yaml @@ -29,7 +29,7 @@ spec: scheme: {{ if not .Values.dashboard.tls.enabled -}}http{{- else -}}https{{ end }} {{- end }} - kind: Rule - match: Host(`{{ .Values.dashboard.ingressRoute.host }}.{{ .Values.domain }}`) && (Path(`/`) || PathPrefix(`/logo`,`/assets/`,`/dashboard.v1alpha1.AuthService/`)) + match: Host(`{{ .Values.dashboard.ingressRoute.host }}.{{ .Values.domain }}`) && (Path(`/`) || PathPrefix(`/logo`,`/assets/`,`/dashboard.v1alpha1.AuthService/`, `/dashboard.v1alpha1.WebAuthnService/`)) priority: 1001 services: - kind: Service diff --git a/charts/cosmo/values.yaml b/charts/cosmo/values.yaml index 0054f010..bf6e3195 100644 --- a/charts/cosmo/values.yaml +++ b/charts/cosmo/values.yaml @@ -192,7 +192,7 @@ dashboard: # but when you are using ArgoCD, these secret keys are changed every sync # because it uses 'helm template' command and lookup function does not work. secretKeys: - immutable: true + immutable: false COOKIE_HASHKEY: COOKIE_BLOCKKEY: COOKIE_SESSION_NAME: diff --git a/go.mod b/go.mod index 35a73f79..4e420eea 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gkampitakis/go-snaps v0.4.3 github.com/go-ldap/ldap/v3 v3.4.4 github.com/go-logr/logr v1.2.4 + github.com/go-webauthn/webauthn v0.8.6 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/gorilla/securecookie v1.1.1 @@ -22,7 +23,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/traefik/traefik/v2 v2.10.1 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.9.0 + golang.org/x/crypto v0.11.0 google.golang.org/protobuf v1.30.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -44,6 +45,7 @@ require ( github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/gkampitakis/ciinfo v0.2.4 // indirect github.com/gkampitakis/go-diff v1.3.2 // indirect github.com/go-acme/lego/v4 v4.10.2 // indirect @@ -55,11 +57,14 @@ require ( github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/go-webauthn/x v0.1.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -75,6 +80,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.50 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -94,6 +100,7 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/traefik/paerser v0.2.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.7.0 // indirect @@ -101,9 +108,9 @@ require ( golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 58420f0c..9a1ecda3 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gkampitakis/ciinfo v0.2.4 h1:Ip1hf4K7ISRuVlDrheuhaeffg1VOhlyeFGaQ/vTxrtE= github.com/gkampitakis/ciinfo v0.2.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= @@ -117,8 +119,14 @@ github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5F github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E= +github.com/go-webauthn/webauthn v0.8.6/go.mod h1:emwVLMCI5yx9evTTvr0r+aOZCdWJqMfbRhF0MufyUog= +github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs= +github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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= @@ -168,6 +176,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -242,6 +252,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -306,8 +318,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -322,6 +334,8 @@ github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= github.com/traefik/traefik/v2 v2.10.1 h1:YP034U+yUkHZizCkokUMdV0wecuROJntxwqH5P70Krs= github.com/traefik/traefik/v2 v2.10.1/go.mod h1:prn4IYzwqrxXeZUfpdl9eICqpcKV9teQD//LFOD/srk= +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/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -355,8 +369,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -494,11 +508,11 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 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= @@ -507,8 +521,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/go.work.sum b/go.work.sum index 458bbf6f..f303ca0e 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,705 +1,58 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -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/bufbuild/connect-go v1.7.0 h1:MGp82v7SCza+3RhsVhV7aMikwxvI3ZfD72YiGt8FYJo= -github.com/bufbuild/connect-go v1.7.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -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= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -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/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ= -github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gkampitakis/ciinfo v0.2.4 h1:Ip1hf4K7ISRuVlDrheuhaeffg1VOhlyeFGaQ/vTxrtE= -github.com/gkampitakis/ciinfo v0.2.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.4.3 h1:0awAg/9gGT63bcaWmigfbGt+tsuIKlI/TiqLP5e+2Cc= -github.com/gkampitakis/go-snaps v0.4.3/go.mod h1:oEL8WdkP4EpHdMA3wrNRjfRiV9wV3OhU/JpX9l4joA0= -github.com/go-acme/lego/v4 v4.10.2 h1:5eW3qmda5v/LP21v1Hj70edKY1jeFZQwO617tdkwp6Q= -github.com/go-acme/lego/v4 v4.10.2/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNypansHGc6U= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -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/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -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/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -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/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -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= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -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/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/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.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= -github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -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/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= -github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ= -github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc= -github.com/traefik/traefik/v2 v2.10.1 h1:YP034U+yUkHZizCkokUMdV0wecuROJntxwqH5P70Krs= -github.com/traefik/traefik/v2 v2.10.1/go.mod h1:prn4IYzwqrxXeZUfpdl9eICqpcKV9teQD//LFOD/srk= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -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/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -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/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -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/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -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= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -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/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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/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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -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-20190108225652-1e06a53dbb7e/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-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= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/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/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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -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-20190425150028-36563e24a262/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/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= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/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-20191108193012-7d206e10da11/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= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -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-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/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/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= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -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/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -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= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -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/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -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-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= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -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/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-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -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/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-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/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/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -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.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -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/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -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/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -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/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/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= -k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= -k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= -k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= -k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= -k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/cli-runtime v0.26.3 h1:3ULe0oI28xmgeLMVXIstB+ZL5CTGvWSMVMLeHxitIuc= -k8s.io/cli-runtime v0.26.3/go.mod h1:5YEhXLV4kLt/OSy9yQwtSSNZU2Z7aTEYta1A+Jg4VC4= -k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= -k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= -k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= -k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= -sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= -sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= -sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/hack/local-run-test/Makefile b/hack/local-run-test/Makefile index 2e222d03..eeccd7d0 100644 --- a/hack/local-run-test/Makefile +++ b/hack/local-run-test/Makefile @@ -378,6 +378,7 @@ run-dashboard-local: --cookie-session-name="$(shell kubectl get secret -n cosmo-system cosmo-auth-env -o=jsonpath={.data.COOKIE_SESSION_NAME} | base64 -d)" \ --cookie-hashkey="$(shell kubectl get secret -n cosmo-system cosmo-auth-env -o=jsonpath={.data.COOKIE_HASHKEY} | base64 -d)" \ --cookie-blockkey="$(shell kubectl get secret -n cosmo-system cosmo-auth-env -o=jsonpath={.data.COOKIE_BLOCKKEY} | base64 -d)" \ + --signin-url=$(DASHBOARD_URL) \ --ldap-url=ldap://localhost:389 \ --ldap-binddn="cn=%s,ou=users,dc=cosmows,dc=dev" \ --insecure diff --git a/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml b/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml index 04f3bffa..03420207 100644 --- a/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml +++ b/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml @@ -1,4 +1,4 @@ -# Generated by cosmoctl - cosmo v1.0.0-rc4 cosmo-workspace 2023 +# Generated by cosmoctl - cosmo v1.0.0-rc5 cosmo-workspace 2023 apiVersion: cosmo-workspace.github.io/v1alpha1 kind: Template metadata: diff --git a/hack/local-run-test/templates/dev-code-server/gryffindor-template.yaml b/hack/local-run-test/templates/dev-code-server/gryffindor-template.yaml index 46f0fdb4..19e7aa96 100644 --- a/hack/local-run-test/templates/dev-code-server/gryffindor-template.yaml +++ b/hack/local-run-test/templates/dev-code-server/gryffindor-template.yaml @@ -1,4 +1,4 @@ -# Generated by cosmoctl - cosmo v1.0.0-rc4 cosmo-workspace 2023 +# Generated by cosmoctl - cosmo v1.0.0-rc5 cosmo-workspace 2023 apiVersion: cosmo-workspace.github.io/v1alpha1 kind: Template metadata: diff --git a/internal/dashboard/__snapshots__/root_test.snap b/internal/dashboard/__snapshots__/root_test.snap index fcea444f..2e82351b 100755 --- a/internal/dashboard/__snapshots__/root_test.snap +++ b/internal/dashboard/__snapshots__/root_test.snap @@ -33,6 +33,7 @@ Flags: --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) --port int Port for dashboard server (default 8443) --serve-dir string Static file dir to serve (default "/app/public") + --signin-url string Dashboard signin url --skip_headers If true, avoid header prefixes in the log messages --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) @@ -85,6 +86,7 @@ Flags: --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) --port int Port for dashboard server (default 8443) --serve-dir string Static file dir to serve (default "/app/public") + --signin-url string Dashboard signin url --skip_headers If true, avoid header prefixes in the log messages --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) @@ -137,6 +139,7 @@ Flags: --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) --port int Port for dashboard server (default 8443) --serve-dir string Static file dir to serve (default "/app/public") + --signin-url string Dashboard signin url --skip_headers If true, avoid header prefixes in the log messages --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) @@ -189,6 +192,7 @@ Flags: --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) --port int Port for dashboard server (default 8443) --serve-dir string Static file dir to serve (default "/app/public") + --signin-url string Dashboard signin url --skip_headers If true, avoid header prefixes in the log messages --skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) --stderrthreshold severity logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) diff --git a/internal/dashboard/__snapshots__/webauthn_handler_test.snap b/internal/dashboard/__snapshots__/webauthn_handler_test.snap new file mode 100644 index 00000000..a85b3b47 --- /dev/null +++ b/internal/dashboard/__snapshots__/webauthn_handler_test.snap @@ -0,0 +1,160 @@ +['Dashboard server [WebAuthn] [ListCredentials] ✅ success in normal context: OK 1'] +SnapShot = """ +{} +""" + +['Dashboard server [WebAuthn] [ListCredentials] ❌ fail with invalid request: empty user name 1'] +SnapShot = 'invalid_argument: invalid ListCredentialsRequest.UserName: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [ListCredentials] ❌ fail with invalid request: user not found 1'] +SnapShot = 'not_found: failed to get user: User.cosmo-workspace.github.io "notfound" not found' + +['Dashboard server [WebAuthn] [Login] ✅ success in begin login but error in finish login: cannot create mock for window.navigator.create() response 1'] +SnapShot = """ +{ + \"publicKey\": { + \"allowCredentials\": [ + { + \"id\": \"RDA5S2M5azR6ZW94RjFCcTFvMGVQdFVwVG5aRE9NRE1Pd1FHblhhaXFUVT0\", + \"type\": \"public-key\" + } + ], + \"challenge\": \"CHALLENGE\", + \"rpId\": \"localhost:8888\", + \"timeout\": 300000, + \"userVerification\": \"preferred\" + } +} +""" + +['Dashboard server [WebAuthn] [Login] ✅ success in begin login but error in finish login: cannot create mock for window.navigator.create() response 2'] +SnapShot = 'internal: User does not own the credential returned' + +['Dashboard server [WebAuthn] [Login] ❌ fail in begin login: empty user name 1'] +SnapShot = 'invalid_argument: invalid BeginLoginRequest.UserName: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [Login] ❌ fail in begin login: user not found 1'] +SnapShot = 'not_found: failed to get user: User.cosmo-workspace.github.io "notfound" not found' + +['Dashboard server [WebAuthn] [Registration] ✅ success in begin registration but error in finish registration: cannot create mock for window.navigator.create() response 1'] +SnapShot = """ +{ + \"publicKey\": { + \"authenticatorSelection\": { + \"requireResidentKey\": false, + \"userVerification\": \"preferred\" + }, + \"challenge\": \"CHALLENGE\", + \"pubKeyCredParams\": [ + { + \"alg\": -7, + \"type\": \"public-key\" + }, + { + \"alg\": -35, + \"type\": \"public-key\" + }, + { + \"alg\": -36, + \"type\": \"public-key\" + }, + { + \"alg\": -257, + \"type\": \"public-key\" + }, + { + \"alg\": -258, + \"type\": \"public-key\" + }, + { + \"alg\": -259, + \"type\": \"public-key\" + }, + { + \"alg\": -37, + \"type\": \"public-key\" + }, + { + \"alg\": -38, + \"type\": \"public-key\" + }, + { + \"alg\": -39, + \"type\": \"public-key\" + }, + { + \"alg\": -8, + \"type\": \"public-key\" + } + ], + \"rp\": { + \"id\": \"localhost:8888\", + \"name\": \"COSMO Dashboard\" + }, + \"timeout\": 300000, + \"user\": { + \"displayName\": \"user\", + \"id\": \"YmZiYzI1OWNkN2MxNmYzZTljMTgwOTFjNWI3YjQ3Njg3ZjJlNDIxN2VlYjA1OGIxYjhhYmUwZTA2NDJiZDhmNw\", + \"name\": \"user\" + } + } +} +""" + +['Dashboard server [WebAuthn] [Registration] ✅ success in begin registration but error in finish registration: cannot create mock for window.navigator.create() response 2'] +SnapShot = 'internal: failed at webauthn create credential: Error validating challenge' + +['Dashboard server [WebAuthn] [Registration] ❌ fail in begin registration: empty user name 1'] +SnapShot = 'invalid_argument: invalid BeginRegistrationRequest.UserName: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [Registration] ❌ fail in begin registration: user not found 1'] +SnapShot = 'not_found: failed to get user: User.cosmo-workspace.github.io "notfound" not found' + +['Dashboard server [WebAuthn] [RemoveCredential] ✅ success in normal context: OK 1'] +SnapShot = """ +{ + \"message\": \"Successfully removed credential\" +} +""" + +['Dashboard server [WebAuthn] [RemoveCredential] ❌ fail with invalid request: credential not found 1'] +SnapShot = 'internal: credential not found' + +['Dashboard server [WebAuthn] [RemoveCredential] ❌ fail with invalid request: empty user name 1'] +SnapShot = 'invalid_argument: invalid DeleteCredentialRequest.UserName: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [RemoveCredential] ❌ fail with invalid request: no credential id 1'] +SnapShot = 'invalid_argument: invalid DeleteCredentialRequest.CredId: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [RemoveCredential] ❌ fail with invalid request: user not found 1'] +SnapShot = 'not_found: failed to get user: User.cosmo-workspace.github.io "notfound" not found' + +['Dashboard server [WebAuthn] [UpdateCredential] ✅ success in normal context: no change 1'] +SnapShot = """ +{ + \"message\": \"Successfully updated credential\" +} +""" + +['Dashboard server [WebAuthn] [UpdateCredential] ✅ success in normal context: update display name 1'] +SnapShot = """ +{ + \"message\": \"Successfully updated credential\" +} +""" + +['Dashboard server [WebAuthn] [UpdateCredential] ✅ success in normal context: update display name to empty 1'] +SnapShot = """ +{ + \"message\": \"Successfully updated credential\" +} +""" + +['Dashboard server [WebAuthn] [UpdateCredential] ❌ fail with invalid request: credential not found 1'] +SnapShot = 'internal: credential not found' + +['Dashboard server [WebAuthn] [UpdateCredential] ❌ fail with invalid request: empty user name 1'] +SnapShot = 'invalid_argument: invalid UpdateCredentialRequest.UserName: value length must be at least 1 runes' + +['Dashboard server [WebAuthn] [UpdateCredential] ❌ fail with invalid request: user not found 1'] +SnapShot = 'not_found: failed to get user: User.cosmo-workspace.github.io "notfound" not found' diff --git a/internal/dashboard/auth_handler.go b/internal/dashboard/auth_handler.go index a641def6..388f5fb9 100644 --- a/internal/dashboard/auth_handler.go +++ b/internal/dashboard/auth_handler.go @@ -23,6 +23,27 @@ func (s *Server) AuthServiceHandler(mux *http.ServeMux) { mux.Handle(path, s.contextMiddleware(handler)) } +func (s *Server) CreateSession(w http.ResponseWriter, r *http.Request, sesInfo session.Info) error { + // Create session + ses, _ := s.sessionStore.New(r, s.CookieSessionName) + ses = session.Set(ses, sesInfo) + + err := s.sessionStore.Save(r, w, ses) + if err != nil { + return fmt.Errorf("failed to save session: %w", err) + } + return nil +} + +func (s *Server) SessionInfo(userName string) (session.Info, time.Time) { + now := time.Now() + expireAt := now.Add(time.Duration(s.MaxAgeSeconds) * time.Second) + return session.Info{ + UserName: userName, + Deadline: expireAt.Unix(), + }, expireAt +} + func (s *Server) Verify(ctx context.Context, req *connect_go.Request[emptypb.Empty]) (*connect_go.Response[dashv1alpha1.VerifyResponse], error) { log := clog.FromContext(ctx).WithCaller() @@ -79,19 +100,8 @@ func (s *Server) Login(ctx context.Context, req *connect_go.Request[dashv1alpha1 } // Create session - now := time.Now() - expireAt := now.Add(time.Duration(s.MaxAgeSeconds) * time.Second) - - ses, _ := s.sessionStore.New(r, s.CookieSessionName) - sesInfo := session.Info{ - UserName: req.Msg.UserName, - Deadline: expireAt.Unix(), - } - log.DebugAll().Info("save session", "userName", sesInfo.UserName, "deadline", sesInfo.Deadline) - ses = session.Set(ses, sesInfo) - - err = s.sessionStore.Save(r, w, ses) - if err != nil { + sesInfo, expireAt := s.SessionInfo(req.Msg.UserName) + if err = s.CreateSession(w, r, sesInfo); err != nil { log.Error(err, "failed to save session") return nil, ErrResponse(log, err) } diff --git a/internal/dashboard/root.go b/internal/dashboard/root.go index fe1c6f0a..a8790ad4 100644 --- a/internal/dashboard/root.go +++ b/internal/dashboard/root.go @@ -14,6 +14,7 @@ import ( "reflect" "time" + "github.com/go-webauthn/webauthn/webauthn" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -54,6 +55,7 @@ type options struct { CookieHashKey string CookieBlockKey string CookieSessionName string + SigninURL string ResponseTimeoutSeconds int64 GracefulShutdownSeconds int64 TLSPrivateKeyPath string @@ -101,6 +103,7 @@ MIT 2023 cosmo-workspace/cosmo rootCmd.PersistentFlags().StringVar(&o.CookieHashKey, "cookie-hashkey", "", "Cookie hashkey") rootCmd.PersistentFlags().StringVar(&o.CookieBlockKey, "cookie-blockkey", "", "Cookie blockkey") rootCmd.PersistentFlags().StringVar(&o.CookieSessionName, "cookie-session-name", "cosmo-auth", "Cookie session name") + rootCmd.PersistentFlags().StringVar(&o.SigninURL, "signin-url", "", "Dashboard signin url") rootCmd.PersistentFlags().StringVar(&o.TLSPrivateKeyPath, "tls-key", "tls.key", "TLS key file path") rootCmd.PersistentFlags().StringVar(&o.TLSCertPath, "tls-cert", "tls.crt", "TLS certificate file path") rootCmd.PersistentFlags().BoolVar(&o.Insecure, "insecure", false, "start http server not https server") @@ -229,6 +232,25 @@ func (o *options) RunE(cmd *cobra.Command, args []string) error { } } + u, err := url.Parse(o.SigninURL) + if err != nil { + panic(fmt.Errorf("failed to parse url: %w", err)) + } + + wconfig := &webauthn.Config{ + RPDisplayName: "COSMO Dashboard", + RPID: o.CookieDomain, + RPOrigins: []string{fmt.Sprintf("https://dashboard.%s", o.CookieDomain), fmt.Sprintf("%s://%s", u.Scheme, u.Host)}, + Debug: true, + } + + wa, err := webauthn.New(wconfig) + if err != nil { + if err != nil { + return fmt.Errorf("failed to create webauthn instance: %w", err) + } + } + serv := &Server{ Log: clog.NewLogger(ctrl.Log.WithName("dashboard")), Klient: klient, @@ -247,6 +269,7 @@ func (o *options) RunE(cmd *cobra.Command, args []string) error { Authorizers: auths, http: &http.Server{Addr: fmt.Sprintf(":%d", o.ServerPort)}, sessionStore: nil, + webauthn: wa, } if err := mgr.Add(serv); err != nil { diff --git a/internal/dashboard/server.go b/internal/dashboard/server.go index 8573d5cf..eaa2648c 100644 --- a/internal/dashboard/server.go +++ b/internal/dashboard/server.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "net/http" + "sync" "time" + "github.com/go-webauthn/webauthn/webauthn" "github.com/gorilla/sessions" cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" @@ -38,6 +40,9 @@ type Server struct { http *http.Server sessionStore sessions.Store + + webauthn *webauthn.WebAuthn + webauthnSessionMap sync.Map } func (s *Server) setupRouter() { @@ -46,6 +51,7 @@ func (s *Server) setupRouter() { // setup proto api s.AuthServiceHandler(mux) + s.WebAuthnServiceHandler(mux) s.UserServiceHandler(mux) s.TemplateServiceHandler(mux) s.WorkspaceServiceHandler(mux) diff --git a/internal/dashboard/suite_test.go b/internal/dashboard/suite_test.go index 297ebf3f..26d967a6 100644 --- a/internal/dashboard/suite_test.go +++ b/internal/dashboard/suite_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/go-webauthn/webauthn/webauthn" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -141,6 +142,15 @@ var _ = BeforeSuite(func() { }, ) + wconfig := &webauthn.Config{ + RPDisplayName: "COSMO Dashboard", + RPID: "localhost:8888", + RPOrigins: []string{"http://localhost:8888"}, + Debug: true, + } + wa, err := webauthn.New(wconfig) + Expect(err).NotTo(HaveOccurred()) + serv := (&Server{ Log: clog.NewLogger(ctrl.Log.WithName("dashboard")), Klient: klient, @@ -157,6 +167,7 @@ var _ = BeforeSuite(func() { CookieBlockKey: "----+----1----+----2----+----3--", CookieSessionName: "test-server", Authorizers: auths, + webauthn: wa, }) err = mgr.Add(serv) Expect(err).NotTo(HaveOccurred()) diff --git a/internal/dashboard/webauthn_handler.go b/internal/dashboard/webauthn_handler.go new file mode 100644 index 00000000..37c48725 --- /dev/null +++ b/internal/dashboard/webauthn_handler.go @@ -0,0 +1,267 @@ +package dashboard + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + connect_go "github.com/bufbuild/connect-go" + webauthnproto "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" + "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/apimachinery/pkg/api/errors" + + cosmowebauthn "github.com/cosmo-workspace/cosmo/pkg/auth/webauthn" + "github.com/cosmo-workspace/cosmo/pkg/clog" + dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" + "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect" +) + +func (s *Server) WebAuthnServiceHandler(mux *http.ServeMux) { + path, handler := dashboardv1alpha1connect.NewWebAuthnServiceHandler(s, + connect_go.WithInterceptors(s.validatorInterceptor())) + mux.Handle(path, s.contextMiddleware(handler)) +} + +func (s *Server) storeWebAuthnSession(sess *webauthn.SessionData) { + s.webauthnSessionMap.Store(string(sess.UserID), sess) +} + +func (s *Server) getWebAuthnSession(id []byte) (webauthn.SessionData, error) { + sess, ok := s.webauthnSessionMap.LoadAndDelete(string(id)) + if !ok { + return webauthn.SessionData{}, fmt.Errorf("session is not found") + } + session, ok := sess.(*webauthn.SessionData) + if !ok { + panic("session is not *webauthn.SessionData") + } + return *session, nil +} + +func convertCredentialsToDashCredentials(creds []cosmowebauthn.Credential) []*dashv1alpha1.Credential { + ret := make([]*dashv1alpha1.Credential, len(creds)) + for i, cred := range creds { + ret[i] = &dashv1alpha1.Credential{ + Id: cred.Base64URLEncodedId, + DisplayName: cred.DisplayName, + Timestamp: timestamppb.New(time.Unix(cred.Timestamp, 0)), + } + } + return ret +} + +func webauthnErr(log *clog.Logger, err error) { + if e, ok := err.(*webauthnproto.Error); ok && e != nil { + log.Error(err, e.DevInfo) + } +} + +// BeginRegistration is proto interface for webauthn.BeginRegistration +func (s *Server) BeginRegistration(ctx context.Context, req *connect_go.Request[dashv1alpha1.BeginRegistrationRequest]) (*connect_go.Response[dashv1alpha1.BeginRegistrationResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + credCreateOpt, session, err := s.webauthn.BeginRegistration(user) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, fmt.Errorf("failed at webauthn begin registration: %w", err)) + } + + s.storeWebAuthnSession(session) + + o, err := json.Marshal(credCreateOpt) + if err != nil { + return nil, ErrResponse(log, fmt.Errorf("failed to serialize credentil creation options: %w", err)) + } + + return connect_go.NewResponse(&dashv1alpha1.BeginRegistrationResponse{ + CredentialCreationOptions: string(o), + }), nil +} + +// FinishRegistration is proto interface for webauthn.FinishRegistration +func (s *Server) FinishRegistration(ctx context.Context, req *connect_go.Request[dashv1alpha1.FinishRegistrationRequest]) (*connect_go.Response[dashv1alpha1.FinishRegistrationResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + log.Debug().Info("fetching webauthn user", "user", req.Msg.UserName) + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + log.Debug().Info("webauthn parse credential creation response", "user", req.Msg.UserName, "response", req.Msg.CredentialCreationResponse) + credCreateRes, err := webauthnproto.ParseCredentialCreationResponseBody(strings.NewReader(req.Msg.CredentialCreationResponse)) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, fmt.Errorf("failed to parse credential creation response: %w", err)) + } + + log.Debug().Info("get begin session from store", "user", req.Msg.UserName, "webAuthnID", user.WebAuthnID()) + session, err := s.getWebAuthnSession(user.WebAuthnID()) + if err != nil { + return nil, ErrResponse(log, fmt.Errorf("failed to get session: %w", err)) + } + + log.Debug().Info("webauthn create credential", "user", req.Msg.UserName) + cred, err := s.webauthn.CreateCredential(user, session, credCreateRes) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, fmt.Errorf("failed at webauthn create credential: %w", err)) + } + log.Info("successfully created and verified credential. saving...", "user", req.Msg.UserName) + + r := requestFromContext(ctx) + + c := cosmowebauthn.Credential{ + DisplayName: r.UserAgent(), + Timestamp: time.Now().Unix(), + Cred: *cred, + } + if err := user.RegisterCredential(ctx, &c); err != nil { + return nil, ErrResponse(log, fmt.Errorf("failed to save credential: %w", err)) + } + log.Info("successfully saved credential", "user", req.Msg.UserName) + + return connect_go.NewResponse(&dashv1alpha1.FinishRegistrationResponse{ + Message: "Successfully registered new credential", + }), nil +} + +// BeginLogin is proto interface for webauthn.BeginLogin +func (s *Server) BeginLogin(ctx context.Context, req *connect_go.Request[dashv1alpha1.BeginLoginRequest]) (*connect_go.Response[dashv1alpha1.BeginLoginResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + log.Debug().Info("fetching webauthn user", "user", req.Msg.UserName) + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + log.Debug().Info("webauthn begin login", "user", req.Msg.UserName) + credAssert, session, err := s.webauthn.BeginLogin(user) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, errors.NewBadRequest(err.Error())) + } + s.storeWebAuthnSession(session) + + a, err := json.Marshal(credAssert) + if err != nil { + return nil, ErrResponse(log, fmt.Errorf("failed to serialize credentila creation options: %w", err)) + } + + return connect_go.NewResponse(&dashv1alpha1.BeginLoginResponse{ + CredentialRequestOptions: string(a), + }), nil +} + +// FinishLogin is proto interface for webauthn.FinishLogin +func (s *Server) FinishLogin(ctx context.Context, req *connect_go.Request[dashv1alpha1.FinishLoginRequest]) (*connect_go.Response[dashv1alpha1.FinishLoginResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + w := responseWriterFromContext(ctx) + r := requestFromContext(ctx) + + log.Debug().Info("fetching webauthn user", "user", req.Msg.UserName) + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + log.Debug().Info("webauthn parse credential request response", "user", req.Msg.UserName, "response", req.Msg.CredentialRequestResult) + credReqRes, err := webauthnproto.ParseCredentialRequestResponseBody(strings.NewReader(req.Msg.CredentialRequestResult)) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, fmt.Errorf("failed to parse credential creation response: %w", err)) + } + + log.Debug().Info("get begin session from store", "user", req.Msg.UserName, "webAuthnID", user.WebAuthnID()) + sess, err := s.getWebAuthnSession(user.WebAuthnID()) + if err != nil { + return nil, ErrResponse(log, err) + } + + log.Debug().Info("webauthn validate login", "user", req.Msg.UserName) + _, err = s.webauthn.ValidateLogin(user, sess, credReqRes) + if err != nil { + webauthnErr(log, err) + return nil, ErrResponse(log, err) + } + + // Create session + sesInfo, expireAt := s.SessionInfo(req.Msg.UserName) + if err = s.CreateSession(w, r, sesInfo); err != nil { + log.Error(err, "failed to save session") + return nil, ErrResponse(log, err) + } + + return connect_go.NewResponse(&dashv1alpha1.FinishLoginResponse{ + Message: "Login Success", + ExpireAt: timestamppb.New(expireAt), + }), nil +} + +// ListCredentials returns all the credentials of a user +func (s *Server) ListCredentials(ctx context.Context, req *connect_go.Request[dashv1alpha1.ListCredentialsRequest]) (*connect_go.Response[dashv1alpha1.ListCredentialsResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + return connect_go.NewResponse(&dashv1alpha1.ListCredentialsResponse{ + Credentials: convertCredentialsToDashCredentials(user.CredentialList.Creds), + }), nil +} + +// UpdateCredential updates credentia +func (s *Server) UpdateCredential(ctx context.Context, req *connect_go.Request[dashv1alpha1.UpdateCredentialRequest]) (*connect_go.Response[dashv1alpha1.UpdateCredentialResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + if err := user.UpdateCredential(ctx, req.Msg.CredId, &req.Msg.CredDisplayName); err != nil { + return nil, ErrResponse(log, err) + } + + return connect_go.NewResponse(&dashv1alpha1.UpdateCredentialResponse{ + Message: "Successfully updated credential", + }), nil +} + +// DeleteCredential remove credential of a user by given id +func (s *Server) DeleteCredential(ctx context.Context, req *connect_go.Request[dashv1alpha1.DeleteCredentialRequest]) (*connect_go.Response[dashv1alpha1.DeleteCredentialResponse], error) { + + log := clog.FromContext(ctx).WithCaller() + + user, err := cosmowebauthn.GetUser(ctx, s.Klient, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + if err := user.RemoveCredential(ctx, req.Msg.CredId); err != nil { + return nil, ErrResponse(log, err) + } + + return connect_go.NewResponse(&dashv1alpha1.DeleteCredentialResponse{ + Message: "Successfully removed credential", + }), nil +} diff --git a/internal/dashboard/webauthn_handler_test.go b/internal/dashboard/webauthn_handler_test.go new file mode 100644 index 00000000..8fad93a1 --- /dev/null +++ b/internal/dashboard/webauthn_handler_test.go @@ -0,0 +1,377 @@ +package dashboard + +import ( + "context" + "encoding/json" + "net/http" + + . "github.com/cosmo-workspace/cosmo/pkg/snap" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/bufbuild/connect-go" + "github.com/go-webauthn/webauthn/webauthn" + + cosmowebauthn "github.com/cosmo-workspace/cosmo/pkg/auth/webauthn" + dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" + "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect" +) + +var _ = Describe("Dashboard server [WebAuthn]", func() { + var ( + userSession string + client dashboardv1alpha1connect.WebAuthnServiceClient + ) + + BeforeEach(func() { + userSession = test_CreateLoginUserSession("normal-user", "user", nil, "password") + client = dashboardv1alpha1connect.NewWebAuthnServiceClient(http.DefaultClient, "http://localhost:8888") + }) + + AfterEach(func() { + clientMock.Clear() + testUtil.DeleteCosmoUserAll() + }) + //================================================================================== + Describe("[ListCredentials]", func() { + + run_test := func(wantErr bool, req *dashv1alpha1.ListCredentialsRequest) { + By("---------------test start----------------") + ctx := context.Background() + res, err := client.ListCredentials(ctx, NewRequestWithSession(req, userSession)) + if err == nil { + Expect(wantErr).To(BeFalse()) + Expect(err).NotTo(HaveOccurred()) + Ω(res.Msg).To(MatchSnapShot()) + } else { + Expect(wantErr).To(BeTrue()) + Expect(res).To(BeNil()) + Ω(err.Error()).To(MatchSnapShot()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in normal context:", + run_test, + Entry("OK", false, &dashv1alpha1.ListCredentialsRequest{UserName: "normal-user"}), + ) + + DescribeTable("❌ fail with invalid request:", + run_test, + Entry("empty user name", true, &dashv1alpha1.ListCredentialsRequest{UserName: ""}), + Entry("user not found", true, &dashv1alpha1.ListCredentialsRequest{UserName: "notfound"}), + ) + }) + + Describe("[Registration]", func() { + + run_test := func(wantErrBegin bool, reqBegin *dashv1alpha1.BeginRegistrationRequest, wantErrFin bool, reqFin *dashv1alpha1.FinishRegistrationRequest) { + By("---------------test start----------------") + ctx := context.Background() + resBegin, err := client.BeginRegistration(ctx, connect.NewRequest(reqBegin)) + if err == nil { + Expect(wantErrBegin).To(BeFalse()) + Expect(err).NotTo(HaveOccurred()) + Ω(webauthnSnapshot(resBegin.Msg.CredentialCreationOptions)).To(MatchSnapShot()) + + By("---------------FinishRegistration----------------") + resFin, err := client.FinishRegistration(ctx, connect.NewRequest(reqFin)) + if err == nil { + Expect(wantErrFin).To(BeFalse()) + Expect(err).NotTo(HaveOccurred()) + Ω(resFin.Msg).To(MatchSnapShot()) + } else { + Expect(wantErrFin).To(BeTrue()) + Expect(resFin).To(BeNil()) + Ω(err.Error()).To(MatchSnapShot()) + } + } else { + Ω(err.Error()).To(MatchSnapShot()) + Expect(resBegin).To(BeNil()) + Expect(wantErrBegin).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in begin registration but error in finish registration:", + run_test, + Entry("cannot create mock for window.navigator.create() response", + false, &dashv1alpha1.BeginRegistrationRequest{UserName: "normal-user"}, + true, &dashv1alpha1.FinishRegistrationRequest{UserName: "normal-user", CredentialCreationResponse: `{ + "id": "SMwLPQP1TEcS0qgAQe4HcaCln24GW7TkehoswjZr9ic", + "rawId": "SMwLPQP1TEcS0qgAQe4HcaCln24GW7TkehoswjZr9ic", + "type": "public-key", + "response": { + "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiOXljQVdNT3lTU0lEX2hJLWtINlV0RFJmSFo4SkJjZE5Tak5QY2dZakFScyIsIm9yaWdpbiI6Imh0dHBzOi8vZGFzaC1rM2QtY29kZS1zZXJ2ZXIuamxhbmRvd25lci5kZXYiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ", + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBZ3clwhlksBwhsD4VrNEScgU34WN1s_gY-oKp89sh82y3RQAAAAAAAAAAAAAAAAAAAAAAAAAAACBIzAs9A_VMRxLSqABB7gdxoKWfbgZbtOR6GizCNmv2J6QBAwM5AQAgWQEAm49eL1TawnIGRRBAo7whklJO8oL0ePOST56sWmY4v8UcMNCcu_2jp9fcrrXaviVcb09TEn5EjrfclEO7s9idBSwoOUvepKavXmnE_6o5SxSNEnG0FwGV5ZAgpgTFwXzSE2OV41VVWaxwwN66TJUYMzojG5zws_FHTsA3TsyiIKxRp1ke2AZ1hyEWhpdwV-Dqs8w2DmFPvslbdu2rJtqSIRRbrmNIFGxWgEL9-CpnE5_7r2Oo2rS4cLB3G8Pq75dlLM5B8_eKdipRveuS08H8dEfQuJ4M1va0yrHZTf_tYkTsoPg3Iu-albVjrr-ZZux42O1JyC_B7YZ_R6EBLApUaSFDAQAB" + } + }`}), + ) + + DescribeTable("❌ fail in begin registration:", + run_test, + Entry("empty user name", true, &dashv1alpha1.BeginRegistrationRequest{UserName: ""}, true, nil), + Entry("user not found", true, &dashv1alpha1.BeginRegistrationRequest{UserName: "notfound"}, true, nil), + ) + }) + + Describe("[Login]", func() { + registerCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + + cred := cosmowebauthn.Credential{ + Base64URLEncodedId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", + DisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36", + Timestamp: 1696436610, + Cred: webauthn.Credential{ + ID: []byte("D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU="), + PublicKey: []byte("pQECAyYgASFYIHYeITFpgzmctVCg/uRgvZWsXxej2aPHG+iiAidcreaiIlggyQy0xtTdTiqYqPlh8SQ0ViQH1vprBBKV9rFZZUhXHxA="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + wu.RegisterCredential(ctx, &cred) + } + removeCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + wu.RemoveCredential(ctx, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU") + } + + run_test := func(wantErrBegin bool, reqBegin *dashv1alpha1.BeginLoginRequest, wantErrFin bool, reqFin *dashv1alpha1.FinishLoginRequest) { + + By("registering credential") + registerCredential() + defer removeCredential() + + By("---------------test start----------------") + ctx := context.Background() + resBegin, err := client.BeginLogin(ctx, connect.NewRequest(reqBegin)) + if err == nil { + Expect(err).NotTo(HaveOccurred()) + Ω(webauthnSnapshot(resBegin.Msg.CredentialRequestOptions)).To(MatchSnapShot()) + Expect(wantErrBegin).To(BeFalse()) + + By("---------------FinishLogin----------------") + resFin, err := client.FinishLogin(ctx, connect.NewRequest(reqFin)) + if err == nil { + Expect(wantErrFin).To(BeFalse()) + Expect(err).NotTo(HaveOccurred()) + Ω(resFin.Msg).To(MatchSnapShot()) + } else { + Expect(wantErrFin).To(BeTrue()) + Expect(resFin).To(BeNil()) + Ω(err.Error()).To(MatchSnapShot()) + } + } else { + Ω(err.Error()).To(MatchSnapShot()) + Expect(resBegin).To(BeNil()) + Expect(wantErrBegin).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in begin login but error in finish login:", + run_test, + Entry("cannot create mock for window.navigator.create() response", + false, &dashv1alpha1.BeginLoginRequest{UserName: "normal-user"}, + true, &dashv1alpha1.FinishLoginRequest{UserName: "normal-user", CredentialRequestResult: `{ + "id": "KxnR0-QIUysgrQAEOKgwwjMnhz45xqyMTxcHaM8DGIQ", + "type": "public-key", + "rawId": "KxnR0-QIUysgrQAEOKgwwjMnhz45xqyMTxcHaM8DGIQ", + "response": { + "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiS09rcEF0UWY0NVFvN28zSml0aWY4VlRJU25ncVEwXzlYVzBXNHVEQl9obyIsIm9yaWdpbiI6Imh0dHBzOi8vZGFzaC1rM2QtY29kZS1zZXJ2ZXIuamxhbmRvd25lci5kZXYiLCJjcm9zc09yaWdpbiI6ZmFsc2V9", + "authenticatorData": "dyXCGWSwHCGwPhWs0RJyBTfhY3Wz-Bj6gqnz2yHzbLcFAAAAAQ", + "signature": "hOOWKeP4EMDW5lM8uP4H_lMG-ZtvxaWycM-KlluSlRcypMyWKukBF3Onng8UDphhmm2lKO-rKDeE92r6cDN3bQ8U16uczAyzWp2oLGCS-tXDR1mQw6sObCEOw8fwxxmmmMokS0V-rcI7QuMTarfiebdBvx-8_imVeB-OD_bj5l9-1UaqdWxVYFLZEtJ1hVEOdKP1bez48qqgYCRngvXNx_NnTol3SJ29W03Pt7FWsh3h2QF1akRLXk9e_XvD5-YZAUOaJCE-4Yqwu_Z45q1e0pcNp_34hBRosFwRZcJOQ5EjRpK4uLyVuKsnBOYrh55kSg_smRisgf1k8ctuGKEJPw", + "userHandle": "ODJmNGI0Yjc2ZDc4Zjg4YTU1OGU5NGU0YmFjYmJkOWRjYzVkYzdhY2JlMmQ5ZjI5ZDRlZTAwZjEwZWZmMWEyMg" + } + }`}), + ) + + DescribeTable("❌ fail in begin login:", + run_test, + Entry("empty user name", true, &dashv1alpha1.BeginLoginRequest{UserName: ""}, true, nil), + Entry("user not found", true, &dashv1alpha1.BeginLoginRequest{UserName: "notfound"}, true, nil), + ) + }) + + Describe("[UpdateCredential]", func() { + registerCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + + cred := cosmowebauthn.Credential{ + Base64URLEncodedId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", + DisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36", + Timestamp: 1696436610, + Cred: webauthn.Credential{ + ID: []byte("D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU="), + PublicKey: []byte("pQECAyYgASFYIHYeITFpgzmctVCg/uRgvZWsXxej2aPHG+iiAidcreaiIlggyQy0xtTdTiqYqPlh8SQ0ViQH1vprBBKV9rFZZUhXHxA="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + wu.RegisterCredential(ctx, &cred) + } + removeCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + wu.RemoveCredential(ctx, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU") + } + + run_test := func(wantErr bool, req *dashv1alpha1.UpdateCredentialRequest) { + + By("registering credential") + registerCredential() + defer removeCredential() + + By("---------------test start----------------") + ctx := context.Background() + res, err := client.UpdateCredential(ctx, NewRequestWithSession(req, userSession)) + if err == nil { + Ω(res.Msg).To(MatchSnapShot()) + Expect(err).NotTo(HaveOccurred()) + Expect(wantErr).To(BeFalse()) + + } else { + Ω(err.Error()).To(MatchSnapShot()) + Expect(res).To(BeNil()) + Expect(wantErr).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in normal context:", + run_test, + Entry("update display name", false, &dashv1alpha1.UpdateCredentialRequest{UserName: "normal-user", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", CredDisplayName: "new display name"}), + Entry("update display name to empty", false, &dashv1alpha1.UpdateCredentialRequest{UserName: "normal-user", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", CredDisplayName: ""}), + Entry("no change", false, &dashv1alpha1.UpdateCredentialRequest{UserName: "normal-user", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", CredDisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"}), + ) + + DescribeTable("❌ fail with invalid request:", + run_test, + Entry("credential not found", true, &dashv1alpha1.UpdateCredentialRequest{UserName: "normal-user", CredId: "notfound", CredDisplayName: "new display name"}), + Entry("empty user name", true, &dashv1alpha1.UpdateCredentialRequest{UserName: "", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", CredDisplayName: "new display name"}), + Entry("user not found", true, &dashv1alpha1.UpdateCredentialRequest{UserName: "notfound", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", CredDisplayName: "new display name"}), + ) + }) + + Describe("[RemoveCredential]", func() { + registerCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + + cred := cosmowebauthn.Credential{ + Base64URLEncodedId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", + DisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36", + Timestamp: 1696436610, + Cred: webauthn.Credential{ + ID: []byte("D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU="), + PublicKey: []byte("pQECAyYgASFYIHYeITFpgzmctVCg/uRgvZWsXxej2aPHG+iiAidcreaiIlggyQy0xtTdTiqYqPlh8SQ0ViQH1vprBBKV9rFZZUhXHxA="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + wu.RegisterCredential(ctx, &cred) + } + removeCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "normal-user") + Expect(err).NotTo(HaveOccurred()) + wu.RemoveCredential(ctx, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU") + } + + run_test := func(wantErr bool, req *dashv1alpha1.DeleteCredentialRequest) { + + By("registering credential") + registerCredential() + defer removeCredential() + + By("---------------test start----------------") + ctx := context.Background() + res, err := client.DeleteCredential(ctx, NewRequestWithSession(req, userSession)) + if err == nil { + Ω(res.Msg).To(MatchSnapShot()) + Expect(err).NotTo(HaveOccurred()) + Expect(wantErr).To(BeFalse()) + + } else { + Ω(err.Error()).To(MatchSnapShot()) + Expect(res).To(BeNil()) + Expect(wantErr).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in normal context:", + run_test, + Entry("OK", false, &dashv1alpha1.DeleteCredentialRequest{UserName: "normal-user", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU"}), + ) + + DescribeTable("❌ fail with invalid request:", + run_test, + Entry("no credential id", true, &dashv1alpha1.DeleteCredentialRequest{UserName: "normal-user", CredId: ""}), + Entry("credential not found", true, &dashv1alpha1.DeleteCredentialRequest{UserName: "normal-user", CredId: "notfound"}), + Entry("empty user name", true, &dashv1alpha1.DeleteCredentialRequest{UserName: "", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU"}), + Entry("user not found", true, &dashv1alpha1.DeleteCredentialRequest{UserName: "notfound", CredId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU"}), + ) + }) +}) + +func webauthnSnapshot(jsondata string) map[string]interface{} { + var v map[string]interface{} + err := json.Unmarshal([]byte(jsondata), &v) + Expect(err).NotTo(HaveOccurred()) + + if vv, ok := v["publicKey"]; ok { + if vvi, ok := vv.(map[string]interface{}); ok { + if _, ok := vvi["challenge"]; ok { + vvi["challenge"] = "CHALLENGE" + } + } + } + + return v +} diff --git a/pkg/auth/webauthn/__snapshots__/credentials_test.snap b/pkg/auth/webauthn/__snapshots__/credentials_test.snap new file mode 100644 index 00000000..ead94b77 --- /dev/null +++ b/pkg/auth/webauthn/__snapshots__/credentials_test.snap @@ -0,0 +1,187 @@ +['WebAuthn [RemoveCredential] ❌ fail with invalid request: credential not found 1'] +SnapShot = 'credential not found' + +['WebAuthn [RemoveCredential] ❌ fail with invalid request: no credential id 1'] +SnapShot = 'credential not found' + +['WebAuthn [UpdateCredential] ❌ fail with invalid request: credential not found 1'] +SnapShot = 'credential not found' + +['WebAuthn should be able to update and delete credentials 1'] +SnapShot = """ +[ + { + \"Base64URLEncodedId\": \"test-cred1\", + \"DisplayName\": \"test-cred1\", + \"Timestamp\": 1650456000, + \"Cred\": { + \"ID\": \"MVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==\", + \"PublicKey\": \"MVFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=\", + \"AttestationType\": \"none\", + \"Transport\": null, + \"Flags\": { + \"UserPresent\": true, + \"UserVerified\": true, + \"BackupEligible\": false, + \"BackupState\": false + }, + \"Authenticator\": { + \"AAGUID\": \"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09\", + \"SignCount\": 0, + \"CloneWarning\": false, + \"Attachment\": \"\" + } + } + }, + { + \"Base64URLEncodedId\": \"test-cred2\", + \"DisplayName\": \"test-cred2\", + \"Timestamp\": 1650542400, + \"Cred\": { + \"ID\": \"MlpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==\", + \"PublicKey\": \"MlFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=\", + \"AttestationType\": \"none\", + \"Transport\": null, + \"Flags\": { + \"UserPresent\": true, + \"UserVerified\": true, + \"BackupEligible\": false, + \"BackupState\": false + }, + \"Authenticator\": { + \"AAGUID\": \"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09\", + \"SignCount\": 0, + \"CloneWarning\": false, + \"Attachment\": \"\" + } + } + } +] +""" + +['WebAuthn should be able to update and delete credentials 2'] +SnapShot = """ +[ + { + \"Base64URLEncodedId\": \"test-cred1\", + \"DisplayName\": \"new name\", + \"Timestamp\": 1650456000, + \"Cred\": { + \"ID\": \"MVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==\", + \"PublicKey\": \"MVFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=\", + \"AttestationType\": \"none\", + \"Transport\": null, + \"Flags\": { + \"UserPresent\": true, + \"UserVerified\": true, + \"BackupEligible\": false, + \"BackupState\": false + }, + \"Authenticator\": { + \"AAGUID\": \"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09\", + \"SignCount\": 0, + \"CloneWarning\": false, + \"Attachment\": \"\" + } + } + } +] +""" + +['WebAuthn should get new WebAuthn User 1'] +SnapShot = """ +\"MDkyYjMwNzhkMzQ5ZWM5MTEwOGRhMThlYWZhM2IyMTRkZjFmZTc1NzcxMTFmMWRmYTBkYzAzZTJkYmU0NmY2Yg==\" +""" + +['WebAuthn should get new WebAuthn User 2'] +SnapShot = 'test-display' + +['WebAuthn should get new WebAuthn User 3'] +SnapShot = 'test-display' + +['WebAuthn should get new WebAuthn User 4'] +SnapShot = '' + +['WebAuthn should get new WebAuthn User 5'] +SnapShot = """ +[] +""" + +['WebAuthn should register new WebAuthn credential for new user 1'] +SnapShot = """ +[] +""" + +['WebAuthn should register new WebAuthn credential for new user 2'] +SnapShot = """ +{ + \"metadata\": { + \"name\": \"cosmo-user-creds\", + \"namespace\": \"cosmo-user-test-user\", + \"creationTimestamp\": null, + \"labels\": { + \"cosmo-workspace.github.io/controller-managed\": \"1\" + } + }, + \"data\": { + \"credentials\": \"eyJDcmVkcyI6W3siQmFzZTY0VVJMRW5jb2RlZElkIjoiUVZwTE1uSm5hMjFxVjJ0M1RGaHJZVXRXUTBaa1FqZDZka2RsYkhOblQxVXZaRUZPT0ZoRmNrNDFSVEZtTUU1bGQwRXpUVTlGUjJaT01WaG1TbWhwVEZkYVVITXlNa05HVDJObVdIWjZRalJNVjNOVk1HOVpQUSIsIkRpc3BsYXlOYW1lIjoidGVzdC1jcmVkIiwiVGltZXN0YW1wIjoxNjUwNDU2MDAwLCJDcmVkIjp7IklEIjoiUVZwTE1uSm5hMjFxVjJ0M1RGaHJZVXRXUTBaa1FqZDZka2RsYkhOblQxVXZaRUZPT0ZoRmNrNDFSVEZtTUU1bGQwRXpUVTlGUjJaT01WaG1TbWhwVEZkYVVITXlNa05HVDJObVdIWjZRalJNVjNOVk1HOVpQUT09IiwiUHVibGljS2V5IjoiY0ZGRlEwRjVXV2RCVTBaWlNVcDJjVE5qZUUxNU5HUjZWMkp2ZUdSWFJITXlNM1F3VEc5dlZFOXpaMkZ4UTBWdllsZDVjRVZtYlRSSmJHZG5kVU5tU21jek5WaElWbWhIU1RKM2FETXJLMk5pVDFOTlRrTXlaSEZPWTA5TU5sVXJZbW9yY1VwRGF6MD0iLCJBdHRlc3RhdGlvblR5cGUiOiJub25lIiwiVHJhbnNwb3J0IjpudWxsLCJGbGFncyI6eyJVc2VyUHJlc2VudCI6dHJ1ZSwiVXNlclZlcmlmaWVkIjp0cnVlLCJCYWNrdXBFbGlnaWJsZSI6ZmFsc2UsIkJhY2t1cFN0YXRlIjpmYWxzZX0sIkF1dGhlbnRpY2F0b3IiOnsiQUFHVUlEIjoiUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFUMDkiLCJTaWduQ291bnQiOjAsIkNsb25lV2FybmluZyI6ZmFsc2UsIkF0dGFjaG1lbnQiOiIifX19XX0=\" + }, + \"type\": \"Opaque\" +} +""" + +['WebAuthn should register new WebAuthn credential for new user 3'] +SnapShot = '{"Creds":[{"Base64URLEncodedId":"QVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ","DisplayName":"test-cred","Timestamp":1650456000,"Cred":{"ID":"QVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==","PublicKey":"cFFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=","AttestationType":"none","Transport":null,"Flags":{"UserPresent":true,"UserVerified":true,"BackupEligible":false,"BackupState":false},"Authenticator":{"AAGUID":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09","SignCount":0,"CloneWarning":false,"Attachment":""}}}]}' + +['WebAuthn should register new WebAuthn credential for new user 4'] +SnapShot = """ +[ + { + \"Base64URLEncodedId\": \"QVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ\", + \"DisplayName\": \"test-cred\", + \"Timestamp\": 1650456000, + \"Cred\": { + \"ID\": \"QVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==\", + \"PublicKey\": \"cFFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=\", + \"AttestationType\": \"none\", + \"Transport\": null, + \"Flags\": { + \"UserPresent\": true, + \"UserVerified\": true, + \"BackupEligible\": false, + \"BackupState\": false + }, + \"Authenticator\": { + \"AAGUID\": \"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09\", + \"SignCount\": 0, + \"CloneWarning\": false, + \"Attachment\": \"\" + } + } + } +] +""" + +['WebAuthn should register new WebAuthn credential for new user 5'] +SnapShot = """ +[ + { + \"ID\": \"QVpLMnJna21qV2t3TFhrYUtWQ0ZkQjd6dkdlbHNnT1UvZEFOOFhFck41RTFmME5ld0EzTU9FR2ZOMVhmSmhpTFdaUHMyMkNGT2NmWHZ6QjRMV3NVMG9ZPQ==\", + \"PublicKey\": \"cFFFQ0F5WWdBU0ZZSUp2cTNjeE15NGR6V2JveGRXRHMyM3QwTG9vVE9zZ2FxQ0VvYld5cEVmbTRJbGdndUNmSmczNVhIVmhHSTJ3aDMrK2NiT1NNTkMyZHFOY09MNlUrYmorcUpDaz0=\", + \"AttestationType\": \"none\", + \"Transport\": null, + \"Flags\": { + \"UserPresent\": true, + \"UserVerified\": true, + \"BackupEligible\": false, + \"BackupState\": false + }, + \"Authenticator\": { + \"AAGUID\": \"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09\", + \"SignCount\": 0, + \"CloneWarning\": false, + \"Attachment\": \"\" + } + } +] +""" diff --git a/pkg/auth/webauthn/credentials.go b/pkg/auth/webauthn/credentials.go new file mode 100644 index 00000000..eb4dcf5b --- /dev/null +++ b/pkg/auth/webauthn/credentials.go @@ -0,0 +1,229 @@ +package webauthn + +import ( + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/go-webauthn/webauthn/webauthn" + "golang.org/x/crypto/argon2" + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" +) + +func GetUser(ctx context.Context, c kosmo.Client, userName string) (*User, error) { + cosmoUser, err := c.GetUser(ctx, userName) + if err != nil { + return nil, err + } + u := User{User: *cosmoUser, client: c} + l, err := NewCredentialList(ctx, c, userName) + if err != nil { + return nil, err + } + u.CredentialList = l + return &u, nil +} + +// User implements webauthn.User interface +// https://pkg.go.dev/github.com/go-webauthn/webauthn@v0.8.6/webauthn#User +type User struct { + cosmov1alpha1.User + CredentialList *CredentialList + + client kosmo.Client +} + +func (u *User) WebAuthnID() []byte { + id := make([]byte, 64) + hashed := argon2.IDKey([]byte(u.Name), nil, 1, 2048, 4, 32) + n := hex.Encode(id, hashed) + if n != 64 { + panic(fmt.Errorf("invalid hash length: n=%d", n)) + } + return id +} +func (u *User) WebAuthnName() string { + return u.Spec.DisplayName +} +func (u *User) WebAuthnDisplayName() string { + return u.Spec.DisplayName +} +func (u *User) WebAuthnCredentials() []webauthn.Credential { + c := make([]webauthn.Credential, len(u.CredentialList.Creds)) + for i, cred := range u.CredentialList.Creds { + c[i] = cred.Cred + } + return c +} + +func (u *User) WebAuthnIcon() string { + return "" +} + +// RegisterCredential store credential to secret +func (u *User) RegisterCredential(ctx context.Context, cred *Credential) error { + cred.Default(time.Now()) + c, err := NewCredentialList(ctx, u.client, u.Name) + if err != nil { + return err + } + err = c.add(cred) + if err != nil { + return err + } + return c.save(ctx) +} + +// RemoveCredential removes credential in secret +func (u *User) RemoveCredential(ctx context.Context, base64urlEncodedCredId string) error { + c, err := NewCredentialList(ctx, u.client, u.Name) + if err != nil { + return err + } + ok := c.remove(base64urlEncodedCredId) + if !ok { + return fmt.Errorf("credential not found") + } + return c.save(ctx) +} + +// UpdateCredential updates credential in secret +func (u *User) UpdateCredential(ctx context.Context, base64urlEncodedCredId string, displayName *string) error { + c, err := NewCredentialList(ctx, u.client, u.Name) + if err != nil { + return err + } + + // update display name if not nil + if displayName != nil { + notfound := true + for i, v := range c.Creds { + if base64urlEncodedCredId == v.Base64URLEncodedId { + c.Creds[i].DisplayName = *displayName + notfound = false + break + } + } + if notfound { + return fmt.Errorf("credential not found") + } + } + return c.save(ctx) +} + +// ListCredentials returns list of registered credentials +func (u *User) ListCredentials(ctx context.Context) ([]Credential, error) { + l, err := NewCredentialList(ctx, u.client, u.Name) + if l == nil || err != nil { + return nil, err + } + return l.Creds, nil +} + +type CredentialList struct { + Creds []Credential + + client kosmo.Client + sec *corev1.Secret +} + +type Credential struct { + Base64URLEncodedId string + DisplayName string + Timestamp int64 + Cred webauthn.Credential +} + +func (c *Credential) Default(now time.Time) { + if c.Base64URLEncodedId == "" { + c.Base64URLEncodedId = base64.RawURLEncoding.EncodeToString(c.Cred.ID) + } + if c.DisplayName == "" { + c.DisplayName = c.Base64URLEncodedId + } + if c.Timestamp == 0 { + c.Timestamp = now.Unix() + } +} + +const ( + CredentialSecretName string = "cosmo-user-creds" + CredentialListKey string = "credentials" +) + +func NewCredentialList(ctx context.Context, c kosmo.Client, userName string) (*CredentialList, error) { + cl := CredentialList{client: c} + var sec corev1.Secret + sec.SetName(CredentialSecretName) + sec.SetNamespace(cosmov1alpha1.UserNamespace(userName)) + cosmov1alpha1.SetControllerManaged(&sec) + + if err := c.Get(ctx, types.NamespacedName{Name: sec.Name, Namespace: sec.Namespace}, &sec); err != nil { + if !apierrs.IsNotFound(err) { + return nil, fmt.Errorf("failed to get credential store: %w", err) + } + } + if len(sec.Data) == 0 { + sec.Data = map[string][]byte{CredentialListKey: []byte(`{"creds": []}`)} + } + if _, ok := sec.Data[CredentialListKey]; !ok { + sec.Data[CredentialListKey] = []byte(`{"creds": []}`) + } + cl.sec = &sec + + if err := json.Unmarshal(sec.Data[CredentialListKey], &cl); err != nil { + return nil, fmt.Errorf("failed to load credential list: %w", err) + } + return &cl, nil +} + +func (c *CredentialList) add(cred *Credential) error { + for _, v := range c.Creds { + if cred.Base64URLEncodedId == v.Base64URLEncodedId { + return errors.New("already exists") + } + } + c.Creds = append(c.Creds, *cred) + return nil +} + +func (c *CredentialList) remove(base64urlEncodedCredId string) bool { + for i, v := range c.Creds { + if base64urlEncodedCredId == v.Base64URLEncodedId { + c.Creds = append(c.Creds[:i], c.Creds[i+1:]...) + return true + } + } + return false +} + +func (c *CredentialList) save(ctx context.Context) error { + raw, err := json.Marshal(c) + if err != nil { + return fmt.Errorf("failed to dump credential list: %w", err) + } + c.sec.Data[CredentialListKey] = raw + return c.updateSecret(ctx) +} + +func (c *CredentialList) updateSecret(ctx context.Context) error { + if err := c.client.Update(ctx, c.sec); err != nil { + if apierrs.IsNotFound(err) { + if err := c.client.Create(ctx, c.sec); err != nil { + return fmt.Errorf("failed to create credential secret: %w", err) + } + } else { + return fmt.Errorf("failed to update credential store: %w", err) + } + } + return nil +} diff --git a/pkg/auth/webauthn/credentials_test.go b/pkg/auth/webauthn/credentials_test.go new file mode 100644 index 00000000..4c92f4ed --- /dev/null +++ b/pkg/auth/webauthn/credentials_test.go @@ -0,0 +1,561 @@ +package webauthn_test + +import ( + "context" + "path/filepath" + "reflect" + "testing" + "time" + + . "github.com/cosmo-workspace/cosmo/pkg/snap" + "github.com/go-webauthn/webauthn/webauthn" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" + cosmowebauthn "github.com/cosmo-workspace/cosmo/pkg/auth/webauthn" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" + testUtil "github.com/cosmo-workspace/cosmo/pkg/kosmo/test" + //+kubebuilder:scaffold:imports +) + +var cfg *rest.Config +var k8sClient kosmo.Client +var t testUtil.TestUtil +var testEnv *envtest.Environment + +func init() { + utilruntime.Must(cosmov1alpha1.AddToScheme(scheme.Scheme)) + //+kubebuilder:scaffold:scheme +} + +func TestWebAuthn(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "WebAuthn Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + k8sClient, err = kosmo.NewClientByRestConfig(cfg, scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + t = testUtil.NewTestUtil(k8sClient) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = Describe("WebAuthn", func() { + + getCredSecret := func(ctx context.Context) (*corev1.Secret, error) { + var sec corev1.Secret + err := k8sClient.Get(ctx, + types.NamespacedName{ + Name: cosmowebauthn.CredentialSecretName, + Namespace: cosmov1alpha1.UserNamespace("test-user")}, &sec) + return &sec, err + } + deleteCredSecret := func(ctx context.Context) error { + var sec corev1.Secret + sec.SetName(cosmowebauthn.CredentialSecretName) + sec.SetNamespace(cosmov1alpha1.UserNamespace("test-user")) + err := k8sClient.Delete(ctx, &sec) + return err + } + var _ = BeforeEach(func() { + t.CreateCosmoUser("test-user", "test-display", nil, cosmov1alpha1.UserAuthTypePasswordSecert) + t.CreateUserNameSpaceandDefaultPasswordIfAbsent("test-user") + }) + + var _ = AfterEach(func() { + ctx := context.Background() + t.DeleteCosmoUserAll() + _, err := k8sClient.GetUser(ctx, "test-user") + Expect(apierrs.IsNotFound(err)).To(BeTrue()) + deleteCredSecret(ctx) + }) + + var _ = It("should get new WebAuthn User", func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + Expect(wu.WebAuthnID()).To(MatchSnapShot()) + Expect(wu.WebAuthnDisplayName()).To(MatchSnapShot()) + Expect(wu.WebAuthnName()).To(MatchSnapShot()) + Expect(wu.WebAuthnIcon()).To(MatchSnapShot()) + Expect(wu.WebAuthnCredentials()).To(MatchSnapShot()) + }) + + var _ = It("should register new WebAuthn credential for new user", func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + By("listing credentials") + creds, err := wu.ListCredentials(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(creds).To(MatchSnapShot()) + + By("fetching credential secret") + _, err = getCredSecret(ctx) + Expect(apierrs.IsNotFound(err)).To(BeTrue()) + + cred := cosmowebauthn.Credential{ + DisplayName: "test-cred", + Timestamp: time.Date(2022, 4, 20, 21, 0, 0, 0, time.Local).Unix(), + Cred: webauthn.Credential{ + ID: []byte("AZK2rgkmjWkwLXkaKVCFdB7zvGelsgOU/dAN8XErN5E1f0NewA3MOEGfN1XfJhiLWZPs22CFOcfXvzB4LWsU0oY="), + PublicKey: []byte("pQECAyYgASFYIJvq3cxMy4dzWboxdWDs23t0LooTOsgaqCEobWypEfm4IlgguCfJg35XHVhGI2wh3++cbOSMNC2dqNcOL6U+bj+qJCk="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("AAAAAAAAAAAAAAAAAAAAAA=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + + By("registering credential") + err = wu.RegisterCredential(ctx, &cred) + Expect(err).NotTo(HaveOccurred()) + + By("fetching credential secret again") + sec, err := getCredSecret(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(ObjectSnapshot(sec)).To(MatchSnapShot()) + + data := sec.Data[cosmowebauthn.CredentialListKey] + Expect(string(data)).To(MatchSnapShot()) + + By("list credentials again") + creds, err = wu.ListCredentials(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(len(creds)).NotTo(BeZero()) + Expect(creds).To(MatchSnapShot()) + + By("get user again") + wu, err = cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + Expect(wu.WebAuthnCredentials()).To(MatchSnapShot()) + }) + + var _ = It("should be able to update and delete credentials", func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + cred1 := cosmowebauthn.Credential{ + Base64URLEncodedId: "test-cred1", + DisplayName: "test-cred1", + Timestamp: time.Date(2022, 4, 20, 21, 0, 0, 0, time.Local).Unix(), + Cred: webauthn.Credential{ + ID: []byte("1ZK2rgkmjWkwLXkaKVCFdB7zvGelsgOU/dAN8XErN5E1f0NewA3MOEGfN1XfJhiLWZPs22CFOcfXvzB4LWsU0oY="), + PublicKey: []byte("1QECAyYgASFYIJvq3cxMy4dzWboxdWDs23t0LooTOsgaqCEobWypEfm4IlgguCfJg35XHVhGI2wh3++cbOSMNC2dqNcOL6U+bj+qJCk="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("AAAAAAAAAAAAAAAAAAAAAA=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + cred2 := cosmowebauthn.Credential{ + Base64URLEncodedId: "test-cred2", + DisplayName: "test-cred2", + Timestamp: time.Date(2022, 4, 21, 21, 0, 0, 0, time.Local).Unix(), + Cred: webauthn.Credential{ + ID: []byte("2ZK2rgkmjWkwLXkaKVCFdB7zvGelsgOU/dAN8XErN5E1f0NewA3MOEGfN1XfJhiLWZPs22CFOcfXvzB4LWsU0oY="), + PublicKey: []byte("2QECAyYgASFYIJvq3cxMy4dzWboxdWDs23t0LooTOsgaqCEobWypEfm4IlgguCfJg35XHVhGI2wh3++cbOSMNC2dqNcOL6U+bj+qJCk="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("AAAAAAAAAAAAAAAAAAAAAA=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + + By("registering credentials") + err = wu.RegisterCredential(ctx, &cred1) + Expect(err).NotTo(HaveOccurred()) + err = wu.RegisterCredential(ctx, &cred2) + Expect(err).NotTo(HaveOccurred()) + + By("registering credentials again return already exists error") + err = wu.RegisterCredential(ctx, &cred2) + Expect(err).To(HaveOccurred()) + + By("list credentials") + creds, err := wu.ListCredentials(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(creds).To(MatchSnapShot()) + + By("update cred1 name") + err = wu.UpdateCredential(ctx, cred1.Base64URLEncodedId, pointer.String("new name")) + Expect(err).NotTo(HaveOccurred()) + + By("remove cred2") + err = wu.RemoveCredential(ctx, cred2.Base64URLEncodedId) + Expect(err).NotTo(HaveOccurred()) + + By("remove cred2 again returns not found error") + err = wu.RemoveCredential(ctx, cred2.Base64URLEncodedId) + Expect(err).To(HaveOccurred()) + + By("list credentials again") + creds, err = wu.ListCredentials(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(creds).To(MatchSnapShot()) + }) + + var _ = It("deals with invalid secret", func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + cred1 := cosmowebauthn.Credential{ + Base64URLEncodedId: "test-cred1", + DisplayName: "test-cred1", + Timestamp: time.Date(2022, 4, 20, 21, 0, 0, 0, time.Local).Unix(), + Cred: webauthn.Credential{ + ID: []byte("1ZK2rgkmjWkwLXkaKVCFdB7zvGelsgOU/dAN8XErN5E1f0NewA3MOEGfN1XfJhiLWZPs22CFOcfXvzB4LWsU0oY="), + PublicKey: []byte("1QECAyYgASFYIJvq3cxMy4dzWboxdWDs23t0LooTOsgaqCEobWypEfm4IlgguCfJg35XHVhGI2wh3++cbOSMNC2dqNcOL6U+bj+qJCk="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("AAAAAAAAAAAAAAAAAAAAAA=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + cred2 := cosmowebauthn.Credential{ + Base64URLEncodedId: "test-cred2", + DisplayName: "test-cred2", + Timestamp: time.Date(2022, 4, 21, 21, 0, 0, 0, time.Local).Unix(), + Cred: webauthn.Credential{ + ID: []byte("2ZK2rgkmjWkwLXkaKVCFdB7zvGelsgOU/dAN8XErN5E1f0NewA3MOEGfN1XfJhiLWZPs22CFOcfXvzB4LWsU0oY="), + PublicKey: []byte("2QECAyYgASFYIJvq3cxMy4dzWboxdWDs23t0LooTOsgaqCEobWypEfm4IlgguCfJg35XHVhGI2wh3++cbOSMNC2dqNcOL6U+bj+qJCk="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("AAAAAAAAAAAAAAAAAAAAAA=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + + By("registering credentials") + err = wu.RegisterCredential(ctx, &cred1) + Expect(err).NotTo(HaveOccurred()) + err = wu.RegisterCredential(ctx, &cred2) + Expect(err).NotTo(HaveOccurred()) + + By("update secret directly with invalid json data") + sec, err := getCredSecret(ctx) + Expect(err).NotTo(HaveOccurred()) + sec.Data[cosmowebauthn.CredentialListKey] = []byte("invalid data") + + err = k8sClient.Update(ctx, sec) + Expect(err).NotTo(HaveOccurred()) + + By("list credentials returns error") + _, err = wu.ListCredentials(ctx) + Expect(err).To(HaveOccurred()) + + By("register credential returns error") + err = wu.RegisterCredential(ctx, &cred2) + Expect(err).To(HaveOccurred()) + + By("remove credential returns error") + err = wu.RemoveCredential(ctx, cred2.Base64URLEncodedId) + Expect(err).To(HaveOccurred()) + }) + + Describe("[UpdateCredential]", func() { + registerCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + cred := cosmowebauthn.Credential{ + Base64URLEncodedId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", + DisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36", + Timestamp: 1696436610, + Cred: webauthn.Credential{ + ID: []byte("D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU="), + PublicKey: []byte("pQECAyYgASFYIHYeITFpgzmctVCg/uRgvZWsXxej2aPHG+iiAidcreaiIlggyQy0xtTdTiqYqPlh8SQ0ViQH1vprBBKV9rFZZUhXHxA="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + wu.RegisterCredential(ctx, &cred) + } + removeCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + wu.RemoveCredential(ctx, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU") + } + + run_test := func(wantErr bool, base64urlEncodedCredId string, displayName *string) { + + By("registering credential") + registerCredential() + defer removeCredential() + + By("---------------test start----------------") + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + err = wu.UpdateCredential(ctx, base64urlEncodedCredId, displayName) + if err == nil { + Expect(err).NotTo(HaveOccurred()) + Expect(wantErr).To(BeFalse()) + + } else { + Expect(err).To(HaveOccurred()) + Ω(err.Error()).To(MatchSnapShot()) + Expect(wantErr).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in normal context:", + run_test, + Entry("update display name", false, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", pointer.String("new display name")), + Entry("update display name to empty", false, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", pointer.String("")), + Entry("update display name to empty", false, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", nil), + Entry("no change", false, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", pointer.String("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36")), + ) + + DescribeTable("❌ fail with invalid request:", + run_test, + Entry("credential not found", true, "notfound", pointer.String("new display name")), + ) + }) + + Describe("[RemoveCredential]", func() { + registerCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + cred := cosmowebauthn.Credential{ + Base64URLEncodedId: "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU", + DisplayName: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36", + Timestamp: 1696436610, + Cred: webauthn.Credential{ + ID: []byte("D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU="), + PublicKey: []byte("pQECAyYgASFYIHYeITFpgzmctVCg/uRgvZWsXxej2aPHG+iiAidcreaiIlggyQy0xtTdTiqYqPlh8SQ0ViQH1vprBBKV9rFZZUhXHxA="), + AttestationType: "none", + Transport: nil, + Flags: webauthn.CredentialFlags{ + UserPresent: true, + UserVerified: true, + BackupEligible: false, + BackupState: false, + }, + Authenticator: webauthn.Authenticator{ + AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), + SignCount: 0, + CloneWarning: false, + Attachment: "", + }, + }, + } + wu.RegisterCredential(ctx, &cred) + } + removeCredential := func() { + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + wu.RemoveCredential(ctx, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU") + } + + run_test := func(wantErr bool, base64urlEncodedCredId string) { + + By("registering credential") + registerCredential() + defer removeCredential() + + By("---------------test start----------------") + ctx := context.Background() + wu, err := cosmowebauthn.GetUser(ctx, k8sClient, "test-user") + Expect(err).NotTo(HaveOccurred()) + + err = wu.RemoveCredential(ctx, base64urlEncodedCredId) + if err == nil { + Expect(err).NotTo(HaveOccurred()) + Expect(wantErr).To(BeFalse()) + + } else { + Expect(err).To(HaveOccurred()) + Ω(err.Error()).To(MatchSnapShot()) + Expect(wantErr).To(BeTrue()) + } + By("---------------test end---------------") + } + + DescribeTable("✅ success in normal context:", + run_test, + Entry("OK", false, "D09Kc9k4zeoxF1Bq1o0ePtUpTnZDOMDMOwQGnXaiqTU"), + ) + + DescribeTable("❌ fail with invalid request:", + run_test, + Entry("no credential id", true, ""), + Entry("credential not found", true, "notfound"), + ) + }) +}) + +func TestCredentials_Default(t *testing.T) { + type fields struct { + Base64URLEncodedId string + DisplayName string + Timestamp int64 + ID string + } + type args struct { + now time.Time + } + tests := []struct { + name string + fields fields + args args + want cosmowebauthn.Credential + }{ + { + name: "defaulting all", + fields: fields{ + ID: "xxxx", + }, + args: args{ + now: time.Date(2022, 4, 20, 9, 0, 0, 0, time.Local), + }, + want: cosmowebauthn.Credential{ + Base64URLEncodedId: "eHh4eA", + DisplayName: "eHh4eA", + Timestamp: 1650412800, + Cred: webauthn.Credential{ + ID: []byte("xxxx"), + }, + }, + }, + { + name: "display name already defined", + fields: fields{ + DisplayName: "defined", + ID: "xxxx", + }, + args: args{ + now: time.Date(2022, 4, 20, 9, 0, 0, 0, time.Local), + }, + want: cosmowebauthn.Credential{ + Base64URLEncodedId: "eHh4eA", + DisplayName: "defined", + Timestamp: 1650412800, + Cred: webauthn.Credential{ + ID: []byte("xxxx"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + c := cosmowebauthn.Credential{ + Base64URLEncodedId: tt.fields.Base64URLEncodedId, + DisplayName: tt.fields.DisplayName, + Timestamp: tt.fields.Timestamp, + Cred: webauthn.Credential{ + ID: []byte(tt.fields.ID), + }, + } + c.Default(tt.args.now) + if !reflect.DeepEqual(c, tt.want) { + t.Errorf("Credential.Default() = %v, want %v", c, tt.want) + } + }) + } +} diff --git a/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/webauthn.connect.go b/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/webauthn.connect.go new file mode 100644 index 00000000..17667071 --- /dev/null +++ b/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/webauthn.connect.go @@ -0,0 +1,278 @@ +// +//WebAuthn protobuf + +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: dashboard/v1alpha1/webauthn.proto + +package dashboardv1alpha1connect + +import ( + context "context" + errors "errors" + connect_go "github.com/bufbuild/connect-go" + v1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect_go.IsAtLeastVersion0_1_0 + +const ( + // WebAuthnServiceName is the fully-qualified name of the WebAuthnService service. + WebAuthnServiceName = "dashboard.v1alpha1.WebAuthnService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // WebAuthnServiceBeginRegistrationProcedure is the fully-qualified name of the WebAuthnService's + // BeginRegistration RPC. + WebAuthnServiceBeginRegistrationProcedure = "/dashboard.v1alpha1.WebAuthnService/BeginRegistration" + // WebAuthnServiceFinishRegistrationProcedure is the fully-qualified name of the WebAuthnService's + // FinishRegistration RPC. + WebAuthnServiceFinishRegistrationProcedure = "/dashboard.v1alpha1.WebAuthnService/FinishRegistration" + // WebAuthnServiceBeginLoginProcedure is the fully-qualified name of the WebAuthnService's + // BeginLogin RPC. + WebAuthnServiceBeginLoginProcedure = "/dashboard.v1alpha1.WebAuthnService/BeginLogin" + // WebAuthnServiceFinishLoginProcedure is the fully-qualified name of the WebAuthnService's + // FinishLogin RPC. + WebAuthnServiceFinishLoginProcedure = "/dashboard.v1alpha1.WebAuthnService/FinishLogin" + // WebAuthnServiceListCredentialsProcedure is the fully-qualified name of the WebAuthnService's + // ListCredentials RPC. + WebAuthnServiceListCredentialsProcedure = "/dashboard.v1alpha1.WebAuthnService/ListCredentials" + // WebAuthnServiceUpdateCredentialProcedure is the fully-qualified name of the WebAuthnService's + // UpdateCredential RPC. + WebAuthnServiceUpdateCredentialProcedure = "/dashboard.v1alpha1.WebAuthnService/UpdateCredential" + // WebAuthnServiceDeleteCredentialProcedure is the fully-qualified name of the WebAuthnService's + // DeleteCredential RPC. + WebAuthnServiceDeleteCredentialProcedure = "/dashboard.v1alpha1.WebAuthnService/DeleteCredential" +) + +// WebAuthnServiceClient is a client for the dashboard.v1alpha1.WebAuthnService service. +type WebAuthnServiceClient interface { + // BeginRegistration returns CredentialCreateOptions to window.navigator.create() which is serialized as JSON string + // Also `publicKey.user.id“ and `publicKey.challenge` are base64url encoded + BeginRegistration(context.Context, *connect_go.Request[v1alpha1.BeginRegistrationRequest]) (*connect_go.Response[v1alpha1.BeginRegistrationResponse], error) + // FinishRegistration check the result of window.navigator.create() + // `rawId`, `response.clientDataJSON` and `response.attestationObject` in the result must be base64url encoded + // and all JSON must be serialized as string + FinishRegistration(context.Context, *connect_go.Request[v1alpha1.FinishRegistrationRequest]) (*connect_go.Response[v1alpha1.FinishRegistrationResponse], error) + // BeginLogin returns CredentialRequestOptions to window.navigator.get() which is serialized as JSON string + // Also `publicKey.allowCredentials[*].id` and `publicKey.challenge` are base64url encoded + BeginLogin(context.Context, *connect_go.Request[v1alpha1.BeginLoginRequest]) (*connect_go.Response[v1alpha1.BeginLoginResponse], error) + // FinishLogin check the result of window.navigator.get() + // `rawId`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, `response.userHandle` + // in the result must be base64url encoded and all JSON must be serialized as string + FinishLogin(context.Context, *connect_go.Request[v1alpha1.FinishLoginRequest]) (*connect_go.Response[v1alpha1.FinishLoginResponse], error) + // ListCredentials returns registered credential ID list + ListCredentials(context.Context, *connect_go.Request[v1alpha1.ListCredentialsRequest]) (*connect_go.Response[v1alpha1.ListCredentialsResponse], error) + // UpdateCredential updates registed credential's human readable infomations + UpdateCredential(context.Context, *connect_go.Request[v1alpha1.UpdateCredentialRequest]) (*connect_go.Response[v1alpha1.UpdateCredentialResponse], error) + // DeleteCredential remove registered credential + DeleteCredential(context.Context, *connect_go.Request[v1alpha1.DeleteCredentialRequest]) (*connect_go.Response[v1alpha1.DeleteCredentialResponse], error) +} + +// NewWebAuthnServiceClient constructs a client for the dashboard.v1alpha1.WebAuthnService service. +// By default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped +// responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the +// connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewWebAuthnServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) WebAuthnServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &webAuthnServiceClient{ + beginRegistration: connect_go.NewClient[v1alpha1.BeginRegistrationRequest, v1alpha1.BeginRegistrationResponse]( + httpClient, + baseURL+WebAuthnServiceBeginRegistrationProcedure, + opts..., + ), + finishRegistration: connect_go.NewClient[v1alpha1.FinishRegistrationRequest, v1alpha1.FinishRegistrationResponse]( + httpClient, + baseURL+WebAuthnServiceFinishRegistrationProcedure, + opts..., + ), + beginLogin: connect_go.NewClient[v1alpha1.BeginLoginRequest, v1alpha1.BeginLoginResponse]( + httpClient, + baseURL+WebAuthnServiceBeginLoginProcedure, + opts..., + ), + finishLogin: connect_go.NewClient[v1alpha1.FinishLoginRequest, v1alpha1.FinishLoginResponse]( + httpClient, + baseURL+WebAuthnServiceFinishLoginProcedure, + opts..., + ), + listCredentials: connect_go.NewClient[v1alpha1.ListCredentialsRequest, v1alpha1.ListCredentialsResponse]( + httpClient, + baseURL+WebAuthnServiceListCredentialsProcedure, + opts..., + ), + updateCredential: connect_go.NewClient[v1alpha1.UpdateCredentialRequest, v1alpha1.UpdateCredentialResponse]( + httpClient, + baseURL+WebAuthnServiceUpdateCredentialProcedure, + opts..., + ), + deleteCredential: connect_go.NewClient[v1alpha1.DeleteCredentialRequest, v1alpha1.DeleteCredentialResponse]( + httpClient, + baseURL+WebAuthnServiceDeleteCredentialProcedure, + opts..., + ), + } +} + +// webAuthnServiceClient implements WebAuthnServiceClient. +type webAuthnServiceClient struct { + beginRegistration *connect_go.Client[v1alpha1.BeginRegistrationRequest, v1alpha1.BeginRegistrationResponse] + finishRegistration *connect_go.Client[v1alpha1.FinishRegistrationRequest, v1alpha1.FinishRegistrationResponse] + beginLogin *connect_go.Client[v1alpha1.BeginLoginRequest, v1alpha1.BeginLoginResponse] + finishLogin *connect_go.Client[v1alpha1.FinishLoginRequest, v1alpha1.FinishLoginResponse] + listCredentials *connect_go.Client[v1alpha1.ListCredentialsRequest, v1alpha1.ListCredentialsResponse] + updateCredential *connect_go.Client[v1alpha1.UpdateCredentialRequest, v1alpha1.UpdateCredentialResponse] + deleteCredential *connect_go.Client[v1alpha1.DeleteCredentialRequest, v1alpha1.DeleteCredentialResponse] +} + +// BeginRegistration calls dashboard.v1alpha1.WebAuthnService.BeginRegistration. +func (c *webAuthnServiceClient) BeginRegistration(ctx context.Context, req *connect_go.Request[v1alpha1.BeginRegistrationRequest]) (*connect_go.Response[v1alpha1.BeginRegistrationResponse], error) { + return c.beginRegistration.CallUnary(ctx, req) +} + +// FinishRegistration calls dashboard.v1alpha1.WebAuthnService.FinishRegistration. +func (c *webAuthnServiceClient) FinishRegistration(ctx context.Context, req *connect_go.Request[v1alpha1.FinishRegistrationRequest]) (*connect_go.Response[v1alpha1.FinishRegistrationResponse], error) { + return c.finishRegistration.CallUnary(ctx, req) +} + +// BeginLogin calls dashboard.v1alpha1.WebAuthnService.BeginLogin. +func (c *webAuthnServiceClient) BeginLogin(ctx context.Context, req *connect_go.Request[v1alpha1.BeginLoginRequest]) (*connect_go.Response[v1alpha1.BeginLoginResponse], error) { + return c.beginLogin.CallUnary(ctx, req) +} + +// FinishLogin calls dashboard.v1alpha1.WebAuthnService.FinishLogin. +func (c *webAuthnServiceClient) FinishLogin(ctx context.Context, req *connect_go.Request[v1alpha1.FinishLoginRequest]) (*connect_go.Response[v1alpha1.FinishLoginResponse], error) { + return c.finishLogin.CallUnary(ctx, req) +} + +// ListCredentials calls dashboard.v1alpha1.WebAuthnService.ListCredentials. +func (c *webAuthnServiceClient) ListCredentials(ctx context.Context, req *connect_go.Request[v1alpha1.ListCredentialsRequest]) (*connect_go.Response[v1alpha1.ListCredentialsResponse], error) { + return c.listCredentials.CallUnary(ctx, req) +} + +// UpdateCredential calls dashboard.v1alpha1.WebAuthnService.UpdateCredential. +func (c *webAuthnServiceClient) UpdateCredential(ctx context.Context, req *connect_go.Request[v1alpha1.UpdateCredentialRequest]) (*connect_go.Response[v1alpha1.UpdateCredentialResponse], error) { + return c.updateCredential.CallUnary(ctx, req) +} + +// DeleteCredential calls dashboard.v1alpha1.WebAuthnService.DeleteCredential. +func (c *webAuthnServiceClient) DeleteCredential(ctx context.Context, req *connect_go.Request[v1alpha1.DeleteCredentialRequest]) (*connect_go.Response[v1alpha1.DeleteCredentialResponse], error) { + return c.deleteCredential.CallUnary(ctx, req) +} + +// WebAuthnServiceHandler is an implementation of the dashboard.v1alpha1.WebAuthnService service. +type WebAuthnServiceHandler interface { + // BeginRegistration returns CredentialCreateOptions to window.navigator.create() which is serialized as JSON string + // Also `publicKey.user.id“ and `publicKey.challenge` are base64url encoded + BeginRegistration(context.Context, *connect_go.Request[v1alpha1.BeginRegistrationRequest]) (*connect_go.Response[v1alpha1.BeginRegistrationResponse], error) + // FinishRegistration check the result of window.navigator.create() + // `rawId`, `response.clientDataJSON` and `response.attestationObject` in the result must be base64url encoded + // and all JSON must be serialized as string + FinishRegistration(context.Context, *connect_go.Request[v1alpha1.FinishRegistrationRequest]) (*connect_go.Response[v1alpha1.FinishRegistrationResponse], error) + // BeginLogin returns CredentialRequestOptions to window.navigator.get() which is serialized as JSON string + // Also `publicKey.allowCredentials[*].id` and `publicKey.challenge` are base64url encoded + BeginLogin(context.Context, *connect_go.Request[v1alpha1.BeginLoginRequest]) (*connect_go.Response[v1alpha1.BeginLoginResponse], error) + // FinishLogin check the result of window.navigator.get() + // `rawId`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, `response.userHandle` + // in the result must be base64url encoded and all JSON must be serialized as string + FinishLogin(context.Context, *connect_go.Request[v1alpha1.FinishLoginRequest]) (*connect_go.Response[v1alpha1.FinishLoginResponse], error) + // ListCredentials returns registered credential ID list + ListCredentials(context.Context, *connect_go.Request[v1alpha1.ListCredentialsRequest]) (*connect_go.Response[v1alpha1.ListCredentialsResponse], error) + // UpdateCredential updates registed credential's human readable infomations + UpdateCredential(context.Context, *connect_go.Request[v1alpha1.UpdateCredentialRequest]) (*connect_go.Response[v1alpha1.UpdateCredentialResponse], error) + // DeleteCredential remove registered credential + DeleteCredential(context.Context, *connect_go.Request[v1alpha1.DeleteCredentialRequest]) (*connect_go.Response[v1alpha1.DeleteCredentialResponse], error) +} + +// NewWebAuthnServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewWebAuthnServiceHandler(svc WebAuthnServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) { + mux := http.NewServeMux() + mux.Handle(WebAuthnServiceBeginRegistrationProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceBeginRegistrationProcedure, + svc.BeginRegistration, + opts..., + )) + mux.Handle(WebAuthnServiceFinishRegistrationProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceFinishRegistrationProcedure, + svc.FinishRegistration, + opts..., + )) + mux.Handle(WebAuthnServiceBeginLoginProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceBeginLoginProcedure, + svc.BeginLogin, + opts..., + )) + mux.Handle(WebAuthnServiceFinishLoginProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceFinishLoginProcedure, + svc.FinishLogin, + opts..., + )) + mux.Handle(WebAuthnServiceListCredentialsProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceListCredentialsProcedure, + svc.ListCredentials, + opts..., + )) + mux.Handle(WebAuthnServiceUpdateCredentialProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceUpdateCredentialProcedure, + svc.UpdateCredential, + opts..., + )) + mux.Handle(WebAuthnServiceDeleteCredentialProcedure, connect_go.NewUnaryHandler( + WebAuthnServiceDeleteCredentialProcedure, + svc.DeleteCredential, + opts..., + )) + return "/dashboard.v1alpha1.WebAuthnService/", mux +} + +// UnimplementedWebAuthnServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedWebAuthnServiceHandler struct{} + +func (UnimplementedWebAuthnServiceHandler) BeginRegistration(context.Context, *connect_go.Request[v1alpha1.BeginRegistrationRequest]) (*connect_go.Response[v1alpha1.BeginRegistrationResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.BeginRegistration is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) FinishRegistration(context.Context, *connect_go.Request[v1alpha1.FinishRegistrationRequest]) (*connect_go.Response[v1alpha1.FinishRegistrationResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.FinishRegistration is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) BeginLogin(context.Context, *connect_go.Request[v1alpha1.BeginLoginRequest]) (*connect_go.Response[v1alpha1.BeginLoginResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.BeginLogin is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) FinishLogin(context.Context, *connect_go.Request[v1alpha1.FinishLoginRequest]) (*connect_go.Response[v1alpha1.FinishLoginResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.FinishLogin is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) ListCredentials(context.Context, *connect_go.Request[v1alpha1.ListCredentialsRequest]) (*connect_go.Response[v1alpha1.ListCredentialsResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.ListCredentials is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) UpdateCredential(context.Context, *connect_go.Request[v1alpha1.UpdateCredentialRequest]) (*connect_go.Response[v1alpha1.UpdateCredentialResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.UpdateCredential is not implemented")) +} + +func (UnimplementedWebAuthnServiceHandler) DeleteCredential(context.Context, *connect_go.Request[v1alpha1.DeleteCredentialRequest]) (*connect_go.Response[v1alpha1.DeleteCredentialResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.WebAuthnService.DeleteCredential is not implemented")) +} diff --git a/proto/gen/dashboard/v1alpha1/webauthn.pb.go b/proto/gen/dashboard/v1alpha1/webauthn.pb.go new file mode 100644 index 00000000..a6918bec --- /dev/null +++ b/proto/gen/dashboard/v1alpha1/webauthn.pb.go @@ -0,0 +1,1228 @@ +// +//WebAuthn protobuf + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: dashboard/v1alpha1/webauthn.proto + +package dashboardv1alpha1 + +import ( + _ "github.com/envoyproxy/protoc-gen-validate/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type BeginRegistrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` +} + +func (x *BeginRegistrationRequest) Reset() { + *x = BeginRegistrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeginRegistrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeginRegistrationRequest) ProtoMessage() {} + +func (x *BeginRegistrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeginRegistrationRequest.ProtoReflect.Descriptor instead. +func (*BeginRegistrationRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{0} +} + +func (x *BeginRegistrationRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +type BeginRegistrationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CredentialCreationOptions string `protobuf:"bytes,1,opt,name=credential_creation_options,json=credentialCreationOptions,proto3" json:"credential_creation_options,omitempty"` +} + +func (x *BeginRegistrationResponse) Reset() { + *x = BeginRegistrationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeginRegistrationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeginRegistrationResponse) ProtoMessage() {} + +func (x *BeginRegistrationResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeginRegistrationResponse.ProtoReflect.Descriptor instead. +func (*BeginRegistrationResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{1} +} + +func (x *BeginRegistrationResponse) GetCredentialCreationOptions() string { + if x != nil { + return x.CredentialCreationOptions + } + return "" +} + +type FinishRegistrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + CredentialCreationResponse string `protobuf:"bytes,2,opt,name=credential_creation_response,json=credentialCreationResponse,proto3" json:"credential_creation_response,omitempty"` +} + +func (x *FinishRegistrationRequest) Reset() { + *x = FinishRegistrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FinishRegistrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishRegistrationRequest) ProtoMessage() {} + +func (x *FinishRegistrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishRegistrationRequest.ProtoReflect.Descriptor instead. +func (*FinishRegistrationRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{2} +} + +func (x *FinishRegistrationRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *FinishRegistrationRequest) GetCredentialCreationResponse() string { + if x != nil { + return x.CredentialCreationResponse + } + return "" +} + +type FinishRegistrationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *FinishRegistrationResponse) Reset() { + *x = FinishRegistrationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FinishRegistrationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishRegistrationResponse) ProtoMessage() {} + +func (x *FinishRegistrationResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishRegistrationResponse.ProtoReflect.Descriptor instead. +func (*FinishRegistrationResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{3} +} + +func (x *FinishRegistrationResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type BeginLoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` +} + +func (x *BeginLoginRequest) Reset() { + *x = BeginLoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeginLoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeginLoginRequest) ProtoMessage() {} + +func (x *BeginLoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeginLoginRequest.ProtoReflect.Descriptor instead. +func (*BeginLoginRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{4} +} + +func (x *BeginLoginRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +type BeginLoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CredentialRequestOptions string `protobuf:"bytes,1,opt,name=credential_request_options,json=credentialRequestOptions,proto3" json:"credential_request_options,omitempty"` +} + +func (x *BeginLoginResponse) Reset() { + *x = BeginLoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeginLoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeginLoginResponse) ProtoMessage() {} + +func (x *BeginLoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeginLoginResponse.ProtoReflect.Descriptor instead. +func (*BeginLoginResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{5} +} + +func (x *BeginLoginResponse) GetCredentialRequestOptions() string { + if x != nil { + return x.CredentialRequestOptions + } + return "" +} + +type FinishLoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + CredentialRequestResult string `protobuf:"bytes,2,opt,name=credential_request_result,json=credentialRequestResult,proto3" json:"credential_request_result,omitempty"` +} + +func (x *FinishLoginRequest) Reset() { + *x = FinishLoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FinishLoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishLoginRequest) ProtoMessage() {} + +func (x *FinishLoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishLoginRequest.ProtoReflect.Descriptor instead. +func (*FinishLoginRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{6} +} + +func (x *FinishLoginRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *FinishLoginRequest) GetCredentialRequestResult() string { + if x != nil { + return x.CredentialRequestResult + } + return "" +} + +type FinishLoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + ExpireAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` +} + +func (x *FinishLoginResponse) Reset() { + *x = FinishLoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FinishLoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinishLoginResponse) ProtoMessage() {} + +func (x *FinishLoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinishLoginResponse.ProtoReflect.Descriptor instead. +func (*FinishLoginResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{7} +} + +func (x *FinishLoginResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *FinishLoginResponse) GetExpireAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpireAt + } + return nil +} + +type ListCredentialsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` +} + +func (x *ListCredentialsRequest) Reset() { + *x = ListCredentialsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialsRequest) ProtoMessage() {} + +func (x *ListCredentialsRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCredentialsRequest.ProtoReflect.Descriptor instead. +func (*ListCredentialsRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{8} +} + +func (x *ListCredentialsRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +type ListCredentialsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Credentials []*Credential `protobuf:"bytes,2,rep,name=credentials,proto3" json:"credentials,omitempty"` +} + +func (x *ListCredentialsResponse) Reset() { + *x = ListCredentialsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialsResponse) ProtoMessage() {} + +func (x *ListCredentialsResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCredentialsResponse.ProtoReflect.Descriptor instead. +func (*ListCredentialsResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{9} +} + +func (x *ListCredentialsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ListCredentialsResponse) GetCredentials() []*Credential { + if x != nil { + return x.Credentials + } + return nil +} + +type Credential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *Credential) Reset() { + *x = Credential{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Credential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Credential) ProtoMessage() {} + +func (x *Credential) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Credential.ProtoReflect.Descriptor instead. +func (*Credential) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{10} +} + +func (x *Credential) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Credential) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *Credential) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +type DeleteCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + CredId string `protobuf:"bytes,2,opt,name=cred_id,json=credId,proto3" json:"cred_id,omitempty"` +} + +func (x *DeleteCredentialRequest) Reset() { + *x = DeleteCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialRequest) ProtoMessage() {} + +func (x *DeleteCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteCredentialRequest.ProtoReflect.Descriptor instead. +func (*DeleteCredentialRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{11} +} + +func (x *DeleteCredentialRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *DeleteCredentialRequest) GetCredId() string { + if x != nil { + return x.CredId + } + return "" +} + +type DeleteCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *DeleteCredentialResponse) Reset() { + *x = DeleteCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialResponse) ProtoMessage() {} + +func (x *DeleteCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteCredentialResponse.ProtoReflect.Descriptor instead. +func (*DeleteCredentialResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{12} +} + +func (x *DeleteCredentialResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type UpdateCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + CredId string `protobuf:"bytes,2,opt,name=cred_id,json=credId,proto3" json:"cred_id,omitempty"` + CredDisplayName string `protobuf:"bytes,3,opt,name=cred_display_name,json=credDisplayName,proto3" json:"cred_display_name,omitempty"` +} + +func (x *UpdateCredentialRequest) Reset() { + *x = UpdateCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialRequest) ProtoMessage() {} + +func (x *UpdateCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateCredentialRequest.ProtoReflect.Descriptor instead. +func (*UpdateCredentialRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateCredentialRequest) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *UpdateCredentialRequest) GetCredId() string { + if x != nil { + return x.CredId + } + return "" +} + +func (x *UpdateCredentialRequest) GetCredDisplayName() string { + if x != nil { + return x.CredDisplayName + } + return "" +} + +type UpdateCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *UpdateCredentialResponse) Reset() { + *x = UpdateCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialResponse) ProtoMessage() {} + +func (x *UpdateCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_webauthn_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateCredentialResponse.ProtoReflect.Descriptor instead. +func (*UpdateCredentialResponse) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP(), []int{14} +} + +func (x *UpdateCredentialResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_dashboard_v1alpha1_webauthn_proto protoreflect.FileDescriptor + +var file_dashboard_v1alpha1_webauthn_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x40, 0x0a, 0x18, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, + 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x5b, 0x0a, 0x19, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x1b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x8c, 0x01, 0x0a, 0x19, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x49, 0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, + 0x02, 0x10, 0x01, 0x52, 0x1a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x36, 0x0a, 0x1a, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x39, 0x0a, 0x11, 0x42, 0x65, 0x67, 0x69, 0x6e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0x52, 0x0a, 0x12, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x7f, 0x0a, 0x12, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x19, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x17, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, + 0x74, 0x22, 0x3e, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, + 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x22, 0x75, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x64, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x79, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x22, 0x61, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, + 0x63, 0x72, 0x65, 0x64, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x8d, 0x01, 0x0a, + 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, + 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x63, 0x72, 0x65, 0x64, 0x49, 0x64, + 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65, + 0x64, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x34, 0x0a, 0x18, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x32, 0xff, 0x05, 0x0a, 0x0f, 0x57, 0x65, 0x62, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x70, 0x0a, 0x11, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x2e, 0x64, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x64, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, + 0x65, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x12, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, + 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, + 0x0a, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x25, 0x2e, 0x64, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0b, 0x46, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x26, 0x2e, 0x64, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x2a, 0x2e, + 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x2b, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xe1, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, + 0x0d, 0x57, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, + 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_dashboard_v1alpha1_webauthn_proto_rawDescOnce sync.Once + file_dashboard_v1alpha1_webauthn_proto_rawDescData = file_dashboard_v1alpha1_webauthn_proto_rawDesc +) + +func file_dashboard_v1alpha1_webauthn_proto_rawDescGZIP() []byte { + file_dashboard_v1alpha1_webauthn_proto_rawDescOnce.Do(func() { + file_dashboard_v1alpha1_webauthn_proto_rawDescData = protoimpl.X.CompressGZIP(file_dashboard_v1alpha1_webauthn_proto_rawDescData) + }) + return file_dashboard_v1alpha1_webauthn_proto_rawDescData +} + +var file_dashboard_v1alpha1_webauthn_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_dashboard_v1alpha1_webauthn_proto_goTypes = []interface{}{ + (*BeginRegistrationRequest)(nil), // 0: dashboard.v1alpha1.BeginRegistrationRequest + (*BeginRegistrationResponse)(nil), // 1: dashboard.v1alpha1.BeginRegistrationResponse + (*FinishRegistrationRequest)(nil), // 2: dashboard.v1alpha1.FinishRegistrationRequest + (*FinishRegistrationResponse)(nil), // 3: dashboard.v1alpha1.FinishRegistrationResponse + (*BeginLoginRequest)(nil), // 4: dashboard.v1alpha1.BeginLoginRequest + (*BeginLoginResponse)(nil), // 5: dashboard.v1alpha1.BeginLoginResponse + (*FinishLoginRequest)(nil), // 6: dashboard.v1alpha1.FinishLoginRequest + (*FinishLoginResponse)(nil), // 7: dashboard.v1alpha1.FinishLoginResponse + (*ListCredentialsRequest)(nil), // 8: dashboard.v1alpha1.ListCredentialsRequest + (*ListCredentialsResponse)(nil), // 9: dashboard.v1alpha1.ListCredentialsResponse + (*Credential)(nil), // 10: dashboard.v1alpha1.Credential + (*DeleteCredentialRequest)(nil), // 11: dashboard.v1alpha1.DeleteCredentialRequest + (*DeleteCredentialResponse)(nil), // 12: dashboard.v1alpha1.DeleteCredentialResponse + (*UpdateCredentialRequest)(nil), // 13: dashboard.v1alpha1.UpdateCredentialRequest + (*UpdateCredentialResponse)(nil), // 14: dashboard.v1alpha1.UpdateCredentialResponse + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp +} +var file_dashboard_v1alpha1_webauthn_proto_depIdxs = []int32{ + 15, // 0: dashboard.v1alpha1.FinishLoginResponse.expire_at:type_name -> google.protobuf.Timestamp + 10, // 1: dashboard.v1alpha1.ListCredentialsResponse.credentials:type_name -> dashboard.v1alpha1.Credential + 15, // 2: dashboard.v1alpha1.Credential.timestamp:type_name -> google.protobuf.Timestamp + 0, // 3: dashboard.v1alpha1.WebAuthnService.BeginRegistration:input_type -> dashboard.v1alpha1.BeginRegistrationRequest + 2, // 4: dashboard.v1alpha1.WebAuthnService.FinishRegistration:input_type -> dashboard.v1alpha1.FinishRegistrationRequest + 4, // 5: dashboard.v1alpha1.WebAuthnService.BeginLogin:input_type -> dashboard.v1alpha1.BeginLoginRequest + 6, // 6: dashboard.v1alpha1.WebAuthnService.FinishLogin:input_type -> dashboard.v1alpha1.FinishLoginRequest + 8, // 7: dashboard.v1alpha1.WebAuthnService.ListCredentials:input_type -> dashboard.v1alpha1.ListCredentialsRequest + 13, // 8: dashboard.v1alpha1.WebAuthnService.UpdateCredential:input_type -> dashboard.v1alpha1.UpdateCredentialRequest + 11, // 9: dashboard.v1alpha1.WebAuthnService.DeleteCredential:input_type -> dashboard.v1alpha1.DeleteCredentialRequest + 1, // 10: dashboard.v1alpha1.WebAuthnService.BeginRegistration:output_type -> dashboard.v1alpha1.BeginRegistrationResponse + 3, // 11: dashboard.v1alpha1.WebAuthnService.FinishRegistration:output_type -> dashboard.v1alpha1.FinishRegistrationResponse + 5, // 12: dashboard.v1alpha1.WebAuthnService.BeginLogin:output_type -> dashboard.v1alpha1.BeginLoginResponse + 7, // 13: dashboard.v1alpha1.WebAuthnService.FinishLogin:output_type -> dashboard.v1alpha1.FinishLoginResponse + 9, // 14: dashboard.v1alpha1.WebAuthnService.ListCredentials:output_type -> dashboard.v1alpha1.ListCredentialsResponse + 14, // 15: dashboard.v1alpha1.WebAuthnService.UpdateCredential:output_type -> dashboard.v1alpha1.UpdateCredentialResponse + 12, // 16: dashboard.v1alpha1.WebAuthnService.DeleteCredential:output_type -> dashboard.v1alpha1.DeleteCredentialResponse + 10, // [10:17] is the sub-list for method output_type + 3, // [3:10] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_dashboard_v1alpha1_webauthn_proto_init() } +func file_dashboard_v1alpha1_webauthn_proto_init() { + if File_dashboard_v1alpha1_webauthn_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_dashboard_v1alpha1_webauthn_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeginRegistrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeginRegistrationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinishRegistrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinishRegistrationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeginLoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeginLoginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinishLoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinishLoginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Credential); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_webauthn_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_dashboard_v1alpha1_webauthn_proto_rawDesc, + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_dashboard_v1alpha1_webauthn_proto_goTypes, + DependencyIndexes: file_dashboard_v1alpha1_webauthn_proto_depIdxs, + MessageInfos: file_dashboard_v1alpha1_webauthn_proto_msgTypes, + }.Build() + File_dashboard_v1alpha1_webauthn_proto = out.File + file_dashboard_v1alpha1_webauthn_proto_rawDesc = nil + file_dashboard_v1alpha1_webauthn_proto_goTypes = nil + file_dashboard_v1alpha1_webauthn_proto_depIdxs = nil +} diff --git a/proto/gen/dashboard/v1alpha1/webauthn.pb.validate.go b/proto/gen/dashboard/v1alpha1/webauthn.pb.validate.go new file mode 100644 index 00000000..ccc256ec --- /dev/null +++ b/proto/gen/dashboard/v1alpha1/webauthn.pb.validate.go @@ -0,0 +1,1796 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: dashboard/v1alpha1/webauthn.proto + +package dashboardv1alpha1 + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on BeginRegistrationRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *BeginRegistrationRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on BeginRegistrationRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// BeginRegistrationRequestMultiError, or nil if none found. +func (m *BeginRegistrationRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *BeginRegistrationRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := BeginRegistrationRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return BeginRegistrationRequestMultiError(errors) + } + + return nil +} + +// BeginRegistrationRequestMultiError is an error wrapping multiple validation +// errors returned by BeginRegistrationRequest.ValidateAll() if the designated +// constraints aren't met. +type BeginRegistrationRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m BeginRegistrationRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m BeginRegistrationRequestMultiError) AllErrors() []error { return m } + +// BeginRegistrationRequestValidationError is the validation error returned by +// BeginRegistrationRequest.Validate if the designated constraints aren't met. +type BeginRegistrationRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e BeginRegistrationRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e BeginRegistrationRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e BeginRegistrationRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e BeginRegistrationRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e BeginRegistrationRequestValidationError) ErrorName() string { + return "BeginRegistrationRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e BeginRegistrationRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sBeginRegistrationRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = BeginRegistrationRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = BeginRegistrationRequestValidationError{} + +// Validate checks the field values on BeginRegistrationResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *BeginRegistrationResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on BeginRegistrationResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// BeginRegistrationResponseMultiError, or nil if none found. +func (m *BeginRegistrationResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *BeginRegistrationResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for CredentialCreationOptions + + if len(errors) > 0 { + return BeginRegistrationResponseMultiError(errors) + } + + return nil +} + +// BeginRegistrationResponseMultiError is an error wrapping multiple validation +// errors returned by BeginRegistrationResponse.ValidateAll() if the +// designated constraints aren't met. +type BeginRegistrationResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m BeginRegistrationResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m BeginRegistrationResponseMultiError) AllErrors() []error { return m } + +// BeginRegistrationResponseValidationError is the validation error returned by +// BeginRegistrationResponse.Validate if the designated constraints aren't met. +type BeginRegistrationResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e BeginRegistrationResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e BeginRegistrationResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e BeginRegistrationResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e BeginRegistrationResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e BeginRegistrationResponseValidationError) ErrorName() string { + return "BeginRegistrationResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e BeginRegistrationResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sBeginRegistrationResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = BeginRegistrationResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = BeginRegistrationResponseValidationError{} + +// Validate checks the field values on FinishRegistrationRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *FinishRegistrationRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FinishRegistrationRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// FinishRegistrationRequestMultiError, or nil if none found. +func (m *FinishRegistrationRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *FinishRegistrationRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := FinishRegistrationRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if utf8.RuneCountInString(m.GetCredentialCreationResponse()) < 1 { + err := FinishRegistrationRequestValidationError{ + field: "CredentialCreationResponse", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return FinishRegistrationRequestMultiError(errors) + } + + return nil +} + +// FinishRegistrationRequestMultiError is an error wrapping multiple validation +// errors returned by FinishRegistrationRequest.ValidateAll() if the +// designated constraints aren't met. +type FinishRegistrationRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FinishRegistrationRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FinishRegistrationRequestMultiError) AllErrors() []error { return m } + +// FinishRegistrationRequestValidationError is the validation error returned by +// FinishRegistrationRequest.Validate if the designated constraints aren't met. +type FinishRegistrationRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FinishRegistrationRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FinishRegistrationRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FinishRegistrationRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FinishRegistrationRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FinishRegistrationRequestValidationError) ErrorName() string { + return "FinishRegistrationRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e FinishRegistrationRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sFinishRegistrationRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FinishRegistrationRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FinishRegistrationRequestValidationError{} + +// Validate checks the field values on FinishRegistrationResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *FinishRegistrationResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FinishRegistrationResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// FinishRegistrationResponseMultiError, or nil if none found. +func (m *FinishRegistrationResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *FinishRegistrationResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + if len(errors) > 0 { + return FinishRegistrationResponseMultiError(errors) + } + + return nil +} + +// FinishRegistrationResponseMultiError is an error wrapping multiple +// validation errors returned by FinishRegistrationResponse.ValidateAll() if +// the designated constraints aren't met. +type FinishRegistrationResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FinishRegistrationResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FinishRegistrationResponseMultiError) AllErrors() []error { return m } + +// FinishRegistrationResponseValidationError is the validation error returned +// by FinishRegistrationResponse.Validate if the designated constraints aren't met. +type FinishRegistrationResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FinishRegistrationResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FinishRegistrationResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FinishRegistrationResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FinishRegistrationResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FinishRegistrationResponseValidationError) ErrorName() string { + return "FinishRegistrationResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e FinishRegistrationResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sFinishRegistrationResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FinishRegistrationResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FinishRegistrationResponseValidationError{} + +// Validate checks the field values on BeginLoginRequest with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *BeginLoginRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on BeginLoginRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// BeginLoginRequestMultiError, or nil if none found. +func (m *BeginLoginRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *BeginLoginRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := BeginLoginRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return BeginLoginRequestMultiError(errors) + } + + return nil +} + +// BeginLoginRequestMultiError is an error wrapping multiple validation errors +// returned by BeginLoginRequest.ValidateAll() if the designated constraints +// aren't met. +type BeginLoginRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m BeginLoginRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m BeginLoginRequestMultiError) AllErrors() []error { return m } + +// BeginLoginRequestValidationError is the validation error returned by +// BeginLoginRequest.Validate if the designated constraints aren't met. +type BeginLoginRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e BeginLoginRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e BeginLoginRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e BeginLoginRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e BeginLoginRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e BeginLoginRequestValidationError) ErrorName() string { + return "BeginLoginRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e BeginLoginRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sBeginLoginRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = BeginLoginRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = BeginLoginRequestValidationError{} + +// Validate checks the field values on BeginLoginResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *BeginLoginResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on BeginLoginResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// BeginLoginResponseMultiError, or nil if none found. +func (m *BeginLoginResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *BeginLoginResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for CredentialRequestOptions + + if len(errors) > 0 { + return BeginLoginResponseMultiError(errors) + } + + return nil +} + +// BeginLoginResponseMultiError is an error wrapping multiple validation errors +// returned by BeginLoginResponse.ValidateAll() if the designated constraints +// aren't met. +type BeginLoginResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m BeginLoginResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m BeginLoginResponseMultiError) AllErrors() []error { return m } + +// BeginLoginResponseValidationError is the validation error returned by +// BeginLoginResponse.Validate if the designated constraints aren't met. +type BeginLoginResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e BeginLoginResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e BeginLoginResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e BeginLoginResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e BeginLoginResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e BeginLoginResponseValidationError) ErrorName() string { + return "BeginLoginResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e BeginLoginResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sBeginLoginResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = BeginLoginResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = BeginLoginResponseValidationError{} + +// Validate checks the field values on FinishLoginRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *FinishLoginRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FinishLoginRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// FinishLoginRequestMultiError, or nil if none found. +func (m *FinishLoginRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *FinishLoginRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := FinishLoginRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if utf8.RuneCountInString(m.GetCredentialRequestResult()) < 1 { + err := FinishLoginRequestValidationError{ + field: "CredentialRequestResult", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return FinishLoginRequestMultiError(errors) + } + + return nil +} + +// FinishLoginRequestMultiError is an error wrapping multiple validation errors +// returned by FinishLoginRequest.ValidateAll() if the designated constraints +// aren't met. +type FinishLoginRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FinishLoginRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FinishLoginRequestMultiError) AllErrors() []error { return m } + +// FinishLoginRequestValidationError is the validation error returned by +// FinishLoginRequest.Validate if the designated constraints aren't met. +type FinishLoginRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FinishLoginRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FinishLoginRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FinishLoginRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FinishLoginRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FinishLoginRequestValidationError) ErrorName() string { + return "FinishLoginRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e FinishLoginRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sFinishLoginRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FinishLoginRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FinishLoginRequestValidationError{} + +// Validate checks the field values on FinishLoginResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *FinishLoginResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FinishLoginResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// FinishLoginResponseMultiError, or nil if none found. +func (m *FinishLoginResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *FinishLoginResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + if all { + switch v := interface{}(m.GetExpireAt()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, FinishLoginResponseValidationError{ + field: "ExpireAt", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, FinishLoginResponseValidationError{ + field: "ExpireAt", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetExpireAt()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return FinishLoginResponseValidationError{ + field: "ExpireAt", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return FinishLoginResponseMultiError(errors) + } + + return nil +} + +// FinishLoginResponseMultiError is an error wrapping multiple validation +// errors returned by FinishLoginResponse.ValidateAll() if the designated +// constraints aren't met. +type FinishLoginResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FinishLoginResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FinishLoginResponseMultiError) AllErrors() []error { return m } + +// FinishLoginResponseValidationError is the validation error returned by +// FinishLoginResponse.Validate if the designated constraints aren't met. +type FinishLoginResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FinishLoginResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FinishLoginResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FinishLoginResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FinishLoginResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FinishLoginResponseValidationError) ErrorName() string { + return "FinishLoginResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e FinishLoginResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sFinishLoginResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FinishLoginResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FinishLoginResponseValidationError{} + +// Validate checks the field values on ListCredentialsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListCredentialsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListCredentialsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListCredentialsRequestMultiError, or nil if none found. +func (m *ListCredentialsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ListCredentialsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := ListCredentialsRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return ListCredentialsRequestMultiError(errors) + } + + return nil +} + +// ListCredentialsRequestMultiError is an error wrapping multiple validation +// errors returned by ListCredentialsRequest.ValidateAll() if the designated +// constraints aren't met. +type ListCredentialsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListCredentialsRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListCredentialsRequestMultiError) AllErrors() []error { return m } + +// ListCredentialsRequestValidationError is the validation error returned by +// ListCredentialsRequest.Validate if the designated constraints aren't met. +type ListCredentialsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListCredentialsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListCredentialsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListCredentialsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListCredentialsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListCredentialsRequestValidationError) ErrorName() string { + return "ListCredentialsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ListCredentialsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListCredentialsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ListCredentialsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListCredentialsRequestValidationError{} + +// Validate checks the field values on ListCredentialsResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListCredentialsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListCredentialsResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListCredentialsResponseMultiError, or nil if none found. +func (m *ListCredentialsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ListCredentialsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + for idx, item := range m.GetCredentials() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListCredentialsResponseValidationError{ + field: fmt.Sprintf("Credentials[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListCredentialsResponseValidationError{ + field: fmt.Sprintf("Credentials[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListCredentialsResponseValidationError{ + field: fmt.Sprintf("Credentials[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ListCredentialsResponseMultiError(errors) + } + + return nil +} + +// ListCredentialsResponseMultiError is an error wrapping multiple validation +// errors returned by ListCredentialsResponse.ValidateAll() if the designated +// constraints aren't met. +type ListCredentialsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListCredentialsResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListCredentialsResponseMultiError) AllErrors() []error { return m } + +// ListCredentialsResponseValidationError is the validation error returned by +// ListCredentialsResponse.Validate if the designated constraints aren't met. +type ListCredentialsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListCredentialsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListCredentialsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListCredentialsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListCredentialsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListCredentialsResponseValidationError) ErrorName() string { + return "ListCredentialsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ListCredentialsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListCredentialsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ListCredentialsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListCredentialsResponseValidationError{} + +// Validate checks the field values on Credential with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *Credential) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Credential with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in CredentialMultiError, or +// nil if none found. +func (m *Credential) ValidateAll() error { + return m.validate(true) +} + +func (m *Credential) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Id + + // no validation rules for DisplayName + + if all { + switch v := interface{}(m.GetTimestamp()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, CredentialValidationError{ + field: "Timestamp", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, CredentialValidationError{ + field: "Timestamp", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetTimestamp()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return CredentialValidationError{ + field: "Timestamp", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return CredentialMultiError(errors) + } + + return nil +} + +// CredentialMultiError is an error wrapping multiple validation errors +// returned by Credential.ValidateAll() if the designated constraints aren't met. +type CredentialMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CredentialMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CredentialMultiError) AllErrors() []error { return m } + +// CredentialValidationError is the validation error returned by +// Credential.Validate if the designated constraints aren't met. +type CredentialValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CredentialValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CredentialValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CredentialValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CredentialValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CredentialValidationError) ErrorName() string { return "CredentialValidationError" } + +// Error satisfies the builtin error interface +func (e CredentialValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCredential.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = CredentialValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CredentialValidationError{} + +// Validate checks the field values on DeleteCredentialRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *DeleteCredentialRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on DeleteCredentialRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// DeleteCredentialRequestMultiError, or nil if none found. +func (m *DeleteCredentialRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *DeleteCredentialRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := DeleteCredentialRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if utf8.RuneCountInString(m.GetCredId()) < 1 { + err := DeleteCredentialRequestValidationError{ + field: "CredId", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return DeleteCredentialRequestMultiError(errors) + } + + return nil +} + +// DeleteCredentialRequestMultiError is an error wrapping multiple validation +// errors returned by DeleteCredentialRequest.ValidateAll() if the designated +// constraints aren't met. +type DeleteCredentialRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m DeleteCredentialRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m DeleteCredentialRequestMultiError) AllErrors() []error { return m } + +// DeleteCredentialRequestValidationError is the validation error returned by +// DeleteCredentialRequest.Validate if the designated constraints aren't met. +type DeleteCredentialRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e DeleteCredentialRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e DeleteCredentialRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e DeleteCredentialRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e DeleteCredentialRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e DeleteCredentialRequestValidationError) ErrorName() string { + return "DeleteCredentialRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e DeleteCredentialRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sDeleteCredentialRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = DeleteCredentialRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = DeleteCredentialRequestValidationError{} + +// Validate checks the field values on DeleteCredentialResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *DeleteCredentialResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on DeleteCredentialResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// DeleteCredentialResponseMultiError, or nil if none found. +func (m *DeleteCredentialResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *DeleteCredentialResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + if len(errors) > 0 { + return DeleteCredentialResponseMultiError(errors) + } + + return nil +} + +// DeleteCredentialResponseMultiError is an error wrapping multiple validation +// errors returned by DeleteCredentialResponse.ValidateAll() if the designated +// constraints aren't met. +type DeleteCredentialResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m DeleteCredentialResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m DeleteCredentialResponseMultiError) AllErrors() []error { return m } + +// DeleteCredentialResponseValidationError is the validation error returned by +// DeleteCredentialResponse.Validate if the designated constraints aren't met. +type DeleteCredentialResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e DeleteCredentialResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e DeleteCredentialResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e DeleteCredentialResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e DeleteCredentialResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e DeleteCredentialResponseValidationError) ErrorName() string { + return "DeleteCredentialResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e DeleteCredentialResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sDeleteCredentialResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = DeleteCredentialResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = DeleteCredentialResponseValidationError{} + +// Validate checks the field values on UpdateCredentialRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateCredentialRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateCredentialRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateCredentialRequestMultiError, or nil if none found. +func (m *UpdateCredentialRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateCredentialRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if utf8.RuneCountInString(m.GetUserName()) < 1 { + err := UpdateCredentialRequestValidationError{ + field: "UserName", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if utf8.RuneCountInString(m.GetCredId()) < 1 { + err := UpdateCredentialRequestValidationError{ + field: "CredId", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + // no validation rules for CredDisplayName + + if len(errors) > 0 { + return UpdateCredentialRequestMultiError(errors) + } + + return nil +} + +// UpdateCredentialRequestMultiError is an error wrapping multiple validation +// errors returned by UpdateCredentialRequest.ValidateAll() if the designated +// constraints aren't met. +type UpdateCredentialRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateCredentialRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateCredentialRequestMultiError) AllErrors() []error { return m } + +// UpdateCredentialRequestValidationError is the validation error returned by +// UpdateCredentialRequest.Validate if the designated constraints aren't met. +type UpdateCredentialRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateCredentialRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateCredentialRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateCredentialRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateCredentialRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateCredentialRequestValidationError) ErrorName() string { + return "UpdateCredentialRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateCredentialRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateCredentialRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = UpdateCredentialRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateCredentialRequestValidationError{} + +// Validate checks the field values on UpdateCredentialResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateCredentialResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateCredentialResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateCredentialResponseMultiError, or nil if none found. +func (m *UpdateCredentialResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateCredentialResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Message + + if len(errors) > 0 { + return UpdateCredentialResponseMultiError(errors) + } + + return nil +} + +// UpdateCredentialResponseMultiError is an error wrapping multiple validation +// errors returned by UpdateCredentialResponse.ValidateAll() if the designated +// constraints aren't met. +type UpdateCredentialResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateCredentialResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateCredentialResponseMultiError) AllErrors() []error { return m } + +// UpdateCredentialResponseValidationError is the validation error returned by +// UpdateCredentialResponse.Validate if the designated constraints aren't met. +type UpdateCredentialResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateCredentialResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateCredentialResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateCredentialResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateCredentialResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateCredentialResponseValidationError) ErrorName() string { + return "UpdateCredentialResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateCredentialResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateCredentialResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = UpdateCredentialResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateCredentialResponseValidationError{} diff --git a/proto/gen/index.md b/proto/gen/index.md index a6a85719..1cd269a0 100644 --- a/proto/gen/index.md +++ b/proto/gen/index.md @@ -46,6 +46,25 @@ - [UserService](#dashboard-v1alpha1-UserService) +- [dashboard/v1alpha1/webauthn.proto](#dashboard_v1alpha1_webauthn-proto) + - [BeginLoginRequest](#dashboard-v1alpha1-BeginLoginRequest) + - [BeginLoginResponse](#dashboard-v1alpha1-BeginLoginResponse) + - [BeginRegistrationRequest](#dashboard-v1alpha1-BeginRegistrationRequest) + - [BeginRegistrationResponse](#dashboard-v1alpha1-BeginRegistrationResponse) + - [Credential](#dashboard-v1alpha1-Credential) + - [DeleteCredentialRequest](#dashboard-v1alpha1-DeleteCredentialRequest) + - [DeleteCredentialResponse](#dashboard-v1alpha1-DeleteCredentialResponse) + - [FinishLoginRequest](#dashboard-v1alpha1-FinishLoginRequest) + - [FinishLoginResponse](#dashboard-v1alpha1-FinishLoginResponse) + - [FinishRegistrationRequest](#dashboard-v1alpha1-FinishRegistrationRequest) + - [FinishRegistrationResponse](#dashboard-v1alpha1-FinishRegistrationResponse) + - [ListCredentialsRequest](#dashboard-v1alpha1-ListCredentialsRequest) + - [ListCredentialsResponse](#dashboard-v1alpha1-ListCredentialsResponse) + - [UpdateCredentialRequest](#dashboard-v1alpha1-UpdateCredentialRequest) + - [UpdateCredentialResponse](#dashboard-v1alpha1-UpdateCredentialResponse) + + - [WebAuthnService](#dashboard-v1alpha1-WebAuthnService) + - [dashboard/v1alpha1/workspace.proto](#dashboard_v1alpha1_workspace-proto) - [NetworkRule](#dashboard-v1alpha1-NetworkRule) - [Workspace](#dashboard-v1alpha1-Workspace) @@ -638,6 +657,272 @@ + +

Top

+ +## dashboard/v1alpha1/webauthn.proto + + + + + +### BeginLoginRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | + + + + + + + + +### BeginLoginResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| credential_request_options | [string](#string) | | | + + + + + + + + +### BeginRegistrationRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | + + + + + + + + +### BeginRegistrationResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| credential_creation_options | [string](#string) | | | + + + + + + + + +### Credential + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | | +| display_name | [string](#string) | | | +| timestamp | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | | + + + + + + + + +### DeleteCredentialRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | +| cred_id | [string](#string) | | | + + + + + + + + +### DeleteCredentialResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| message | [string](#string) | | | + + + + + + + + +### FinishLoginRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | +| credential_request_result | [string](#string) | | | + + + + + + + + +### FinishLoginResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| message | [string](#string) | | | +| expire_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | | + + + + + + + + +### FinishRegistrationRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | +| credential_creation_response | [string](#string) | | | + + + + + + + + +### FinishRegistrationResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| message | [string](#string) | | | + + + + + + + + +### ListCredentialsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | + + + + + + + + +### ListCredentialsResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| message | [string](#string) | | | +| credentials | [Credential](#dashboard-v1alpha1-Credential) | repeated | | + + + + + + + + +### UpdateCredentialRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| user_name | [string](#string) | | | +| cred_id | [string](#string) | | | +| cred_display_name | [string](#string) | | | + + + + + + + + +### UpdateCredentialResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| message | [string](#string) | | | + + + + + + + + + + + + + + +### WebAuthnService + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| BeginRegistration | [BeginRegistrationRequest](#dashboard-v1alpha1-BeginRegistrationRequest) | [BeginRegistrationResponse](#dashboard-v1alpha1-BeginRegistrationResponse) | BeginRegistration returns CredentialCreateOptions to window.navigator.create() which is serialized as JSON string Also `publicKey.user.id`` and `publicKey.challenge` are base64url encoded | +| FinishRegistration | [FinishRegistrationRequest](#dashboard-v1alpha1-FinishRegistrationRequest) | [FinishRegistrationResponse](#dashboard-v1alpha1-FinishRegistrationResponse) | FinishRegistration check the result of window.navigator.create() `rawId`, `response.clientDataJSON` and `response.attestationObject` in the result must be base64url encoded and all JSON must be serialized as string | +| BeginLogin | [BeginLoginRequest](#dashboard-v1alpha1-BeginLoginRequest) | [BeginLoginResponse](#dashboard-v1alpha1-BeginLoginResponse) | BeginLogin returns CredentialRequestOptions to window.navigator.get() which is serialized as JSON string Also `publicKey.allowCredentials[*].id` and `publicKey.challenge` are base64url encoded | +| FinishLogin | [FinishLoginRequest](#dashboard-v1alpha1-FinishLoginRequest) | [FinishLoginResponse](#dashboard-v1alpha1-FinishLoginResponse) | FinishLogin check the result of window.navigator.get() `rawId`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, `response.userHandle` in the result must be base64url encoded and all JSON must be serialized as string | +| ListCredentials | [ListCredentialsRequest](#dashboard-v1alpha1-ListCredentialsRequest) | [ListCredentialsResponse](#dashboard-v1alpha1-ListCredentialsResponse) | ListCredentials returns registered credential ID list | +| UpdateCredential | [UpdateCredentialRequest](#dashboard-v1alpha1-UpdateCredentialRequest) | [UpdateCredentialResponse](#dashboard-v1alpha1-UpdateCredentialResponse) | UpdateCredential updates registed credential's human readable infomations | +| DeleteCredential | [DeleteCredentialRequest](#dashboard-v1alpha1-DeleteCredentialRequest) | [DeleteCredentialResponse](#dashboard-v1alpha1-DeleteCredentialResponse) | DeleteCredential remove registered credential | + + + + +

Top

diff --git a/proto/proto/dashboard-apis/dashboard/v1alpha1/webauthn.proto b/proto/proto/dashboard-apis/dashboard/v1alpha1/webauthn.proto new file mode 100644 index 00000000..bc37e5f5 --- /dev/null +++ b/proto/proto/dashboard-apis/dashboard/v1alpha1/webauthn.proto @@ -0,0 +1,104 @@ +/* + WebAuthn protobuf +*/ + +syntax = "proto3"; + +package dashboard.v1alpha1; + +import "google/protobuf/timestamp.proto"; +import "validate/validate.proto"; + +service WebAuthnService { + // BeginRegistration returns CredentialCreateOptions to window.navigator.create() which is serialized as JSON string + // Also `publicKey.user.id`` and `publicKey.challenge` are base64url encoded + rpc BeginRegistration(BeginRegistrationRequest) returns (BeginRegistrationResponse); + // FinishRegistration check the result of window.navigator.create() + // `rawId`, `response.clientDataJSON` and `response.attestationObject` in the result must be base64url encoded + // and all JSON must be serialized as string + rpc FinishRegistration(FinishRegistrationRequest) returns (FinishRegistrationResponse); + // BeginLogin returns CredentialRequestOptions to window.navigator.get() which is serialized as JSON string + // Also `publicKey.allowCredentials[*].id` and `publicKey.challenge` are base64url encoded + rpc BeginLogin(BeginLoginRequest) returns (BeginLoginResponse); + // FinishLogin check the result of window.navigator.get() + // `rawId`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, `response.userHandle` + // in the result must be base64url encoded and all JSON must be serialized as string + rpc FinishLogin(FinishLoginRequest) returns (FinishLoginResponse); + + // ListCredentials returns registered credential ID list + rpc ListCredentials(ListCredentialsRequest) returns (ListCredentialsResponse); + // UpdateCredential updates registed credential's human readable infomations + rpc UpdateCredential(UpdateCredentialRequest) returns (UpdateCredentialResponse); + // DeleteCredential remove registered credential + rpc DeleteCredential(DeleteCredentialRequest) returns (DeleteCredentialResponse); +} + +message BeginRegistrationRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; +} + +message BeginRegistrationResponse { + string credential_creation_options = 1; +} + +message FinishRegistrationRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; + string credential_creation_response = 2 [(validate.rules).string = { min_len: 1 }]; +} + +message FinishRegistrationResponse { + string message = 1; +} + +message BeginLoginRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; +} + +message BeginLoginResponse { + string credential_request_options = 1; +} + +message FinishLoginRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; + string credential_request_result = 2 [(validate.rules).string = { min_len: 1 }]; +} + +message FinishLoginResponse { + string message = 1; + google.protobuf.Timestamp expire_at = 2; +} + +message ListCredentialsRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }];; +} + +message ListCredentialsResponse { + string message = 1; + repeated Credential credentials = 2; +} + +message Credential { + string id = 1; + string display_name = 2; + google.protobuf.Timestamp timestamp = 3; +} + + +message DeleteCredentialRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; + string cred_id = 2 [(validate.rules).string = { min_len: 1 }]; +} + +message DeleteCredentialResponse { + string message = 1; +} + +message UpdateCredentialRequest { + string user_name = 1 [(validate.rules).string = { min_len: 1 }]; + string cred_id = 2 [(validate.rules).string = { min_len: 1 }]; + string cred_display_name = 3; +} + +message UpdateCredentialResponse { + string message = 1; +} diff --git a/web/dashboard-ui/package.json b/web/dashboard-ui/package.json index 27709be2..1f754f97 100644 --- a/web/dashboard-ui/package.json +++ b/web/dashboard-ui/package.json @@ -21,6 +21,7 @@ "@mui/icons-material": "5.14.3", "@mui/material": "5.14.5", "@types/node": "20.5.1", + "base64url": "^3.0.1", "copy-to-clipboard": "3.3.3", "notistack": "3.0.1", "react": "18.2.0", @@ -47,4 +48,4 @@ "vite": "4.4.9", "vitest": "0.34.2" } -} \ No newline at end of file +} diff --git a/web/dashboard-ui/src/App.tsx b/web/dashboard-ui/src/App.tsx index f85dc4ae..fa3b4470 100644 --- a/web/dashboard-ui/src/App.tsx +++ b/web/dashboard-ui/src/App.tsx @@ -8,6 +8,7 @@ import { LoginProvider } from './components/LoginProvider'; import { MyThemeProvider } from './components/MyThemeProvider'; import { PageSettingsProvider } from './components/PageSettingsProvider'; import { ProgressProvider } from './components/ProgressProvider'; +import { AuthenticatorManageDialogContext } from './views/organisms/AuthenticatorManageDialog'; import { PasswordChangeDialogContext } from './views/organisms/PasswordChangeDialog'; import { UserInfoDialogContext } from './views/organisms/UserActionDialog'; import { UserAddonChangeDialogContext } from './views/organisms/UserAddonsChangeDialog'; @@ -68,13 +69,15 @@ function App() { - - - - - - - + + + + + + + + + diff --git a/web/dashboard-ui/src/__tests__/components/Base64.spec.ts b/web/dashboard-ui/src/__tests__/components/Base64.spec.ts new file mode 100644 index 00000000..625c09ab --- /dev/null +++ b/web/dashboard-ui/src/__tests__/components/Base64.spec.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { base64url } from '../../components/Base64'; + +//----------------------------------------------- +// test +//----------------------------------------------- + +function toArrayBuffer(buffer) { + const arrayBuffer = new ArrayBuffer(buffer.length); + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + return arrayBuffer; +} + +function toBuffer(arrayBuffer) { + const buffer = Buffer.alloc(arrayBuffer.byteLength); + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + return buffer; +} + +describe('base64url', () => { + describe('encode', () => { + it('✅ ok', async () => { + const tests = [ + { + name: "1", + input: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + }, + { + name: "1", + input: "a", + }, + { + name: "2", + input: "aa", + }, + { + name: "3", + input: "aaa", + }, + { + name: "4", + input: "aaaa", + }, + { + name: "5", + input: "aaaaa", + }, + ]; + for (const t of tests) { + const raw = Buffer.from(t.input, 'utf8'); + const got = base64url.encode(raw); + + const want = raw.toString('base64url'); + expect(got).toEqual(want); + } + }); + }); + + describe('decode', () => { + it('✅ ok', async () => { + const base64str = 'QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw=='; + const got = base64url.decode(base64str); + const want = Buffer.from(base64str, 'base64url'); + expect(got).toEqual(toArrayBuffer(want)); + }); + + it('✅ ok: no padding', async () => { + const base64strWithPadding = 'QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw=='; + const want = Buffer.from(base64strWithPadding, 'base64url'); + + const base64strNoPadding = 'QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw'; + const got = base64url.decode(base64strNoPadding); + expect(got).toEqual(toArrayBuffer(want)); + }); + }); + +}); diff --git a/web/dashboard-ui/src/components/Base64.ts b/web/dashboard-ui/src/components/Base64.ts new file mode 100644 index 00000000..00edbc05 --- /dev/null +++ b/web/dashboard-ui/src/components/Base64.ts @@ -0,0 +1,58 @@ +// https://github.com/google/webauthndemo/blob/main/src/public/scripts/base64url.ts + +const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + +// Use a lookup table to find the index. +const lookup = new Uint8Array(256); +for (let i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; +} + +const encode = function ( + arraybuffer: ArrayBuffer +): string { + const bytes = new Uint8Array(arraybuffer); + const len = bytes.length; + let base64 = ''; + + for (let 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; +}; + +const decode = function ( + base64: string +): ArrayBuffer { + const len = base64.length; + const bufferLength = base64.length * 0.75; + const arraybuffer = new ArrayBuffer(bufferLength); + const bytes = new Uint8Array(arraybuffer); + + let p = 0; + for (let i = 0; i < len; i += 4) { + const encoded1 = lookup[base64.charCodeAt(i)]; + const encoded2 = lookup[base64.charCodeAt(i + 1)]; + const encoded3 = lookup[base64.charCodeAt(i + 2)]; + const 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; +}; + +const base64url = { encode, decode }; +export { base64url }; diff --git a/web/dashboard-ui/src/components/LoginProvider.tsx b/web/dashboard-ui/src/components/LoginProvider.tsx index 3e661977..d923f92c 100644 --- a/web/dashboard-ui/src/components/LoginProvider.tsx +++ b/web/dashboard-ui/src/components/LoginProvider.tsx @@ -2,7 +2,8 @@ import { Box, CircularProgress, CssBaseline, Stack, Typography } from '@mui/mate import { useSnackbar } from 'notistack'; import React, { createContext, useContext, useEffect, useState } from 'react'; import { User } from '../proto/gen/dashboard/v1alpha1/user_pb'; -import { useAuthService, useUserService } from '../services/DashboardServices'; +import { useAuthService, useUserService, useWebAuthnService } from '../services/DashboardServices'; +import { base64url } from './Base64'; import { useProgress } from './ProgressProvider'; @@ -26,6 +27,67 @@ const useLoginModule = () => { const { setMask, releaseMask } = useProgress(); const userService = useUserService(); const authService = useAuthService(); + const webauthnService = useWebAuthnService(); + + /** + * loginWithWebAuthn + */ + const loginWithWebAuthn = async (userName: string) => { + console.log('loginWithWebAuthn start'); + try { + const credId = localStorage.getItem(`credId`); + if (credId === null) { + throw Error('credId is null'); + } + + const beginResp = await webauthnService.beginLogin({ userName: userName }); + const options = JSON.parse(beginResp.credentialRequestOptions); + + const opt: CredentialRequestOptions = JSON.parse(JSON.stringify(options)); + + if (options.publicKey?.challenge) { + opt.publicKey!.challenge = base64url.decode(options.publicKey?.challenge); + } + + let allowed = false; + for (let index = 0; index < options.publicKey?.allowCredentials.length; index++) { + if (options.publicKey?.allowCredentials[index].id === credId) { allowed = true }; + if (options.publicKey?.allowCredentials) { + opt.publicKey!.allowCredentials![index].id = base64url.decode(options.publicKey?.allowCredentials[index].id); + } + } + if (!allowed) { throw Error('invalid credentials'); } + + // Credential is allowed to access only id and type so use any. + const cred: any = await navigator.credentials.get(opt); + if (cred === null) { + console.log("cred is null"); + throw Error('credential is null'); + } + + const credential = { + id: cred.id, + type: cred.type, + rawId: base64url.encode(cred.rawId), + response: { + clientDataJSON: base64url.encode(cred.response.clientDataJSON), + authenticatorData: base64url.encode(cred.response.authenticatorData), + signature: base64url.encode(cred.response.signature), + userHandle: base64url.encode(cred.response.userHandle), + }, + }; + + const finResp = await webauthnService.finishLogin({ userName: userName, credentialRequestResult: JSON.stringify(credential) }); + await getMyUserInfo(userName); + console.log('loginWithWebAuthn end', finResp); + return; + } + catch (error) { + setLoginUser(undefined); + error instanceof DOMException || handleError(error); + throw error; + } + } /** @@ -159,6 +221,7 @@ const useLoginModule = () => { loginUser, verifyLogin, login, + loginWithWebAuthn, logout, refreshUserInfo, updataPassword, diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_connectweb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_connectweb.ts new file mode 100644 index 00000000..832a512c --- /dev/null +++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_connectweb.ts @@ -0,0 +1,103 @@ +// +//WebAuthn protobuf + +// @generated by protoc-gen-connect-web v0.8.6 with parameter "target=ts" +// @generated from file dashboard/v1alpha1/webauthn.proto (package dashboard.v1alpha1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { BeginLoginRequest, BeginLoginResponse, BeginRegistrationRequest, BeginRegistrationResponse, DeleteCredentialRequest, DeleteCredentialResponse, FinishLoginRequest, FinishLoginResponse, FinishRegistrationRequest, FinishRegistrationResponse, ListCredentialsRequest, ListCredentialsResponse, UpdateCredentialRequest, UpdateCredentialResponse } from "./webauthn_pb.js"; +import { MethodKind } from "@bufbuild/protobuf"; + +/** + * @generated from service dashboard.v1alpha1.WebAuthnService + */ +export const WebAuthnService = { + typeName: "dashboard.v1alpha1.WebAuthnService", + methods: { + /** + * BeginRegistration returns CredentialCreateOptions to window.navigator.create() which is serialized as JSON string + * Also `publicKey.user.id`` and `publicKey.challenge` are base64url encoded + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.BeginRegistration + */ + beginRegistration: { + name: "BeginRegistration", + I: BeginRegistrationRequest, + O: BeginRegistrationResponse, + kind: MethodKind.Unary, + }, + /** + * FinishRegistration check the result of window.navigator.create() + * `rawId`, `response.clientDataJSON` and `response.attestationObject` in the result must be base64url encoded + * and all JSON must be serialized as string + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.FinishRegistration + */ + finishRegistration: { + name: "FinishRegistration", + I: FinishRegistrationRequest, + O: FinishRegistrationResponse, + kind: MethodKind.Unary, + }, + /** + * BeginLogin returns CredentialRequestOptions to window.navigator.get() which is serialized as JSON string + * Also `publicKey.allowCredentials[*].id` and `publicKey.challenge` are base64url encoded + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.BeginLogin + */ + beginLogin: { + name: "BeginLogin", + I: BeginLoginRequest, + O: BeginLoginResponse, + kind: MethodKind.Unary, + }, + /** + * FinishLogin check the result of window.navigator.get() + * `rawId`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, `response.userHandle` + * in the result must be base64url encoded and all JSON must be serialized as string + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.FinishLogin + */ + finishLogin: { + name: "FinishLogin", + I: FinishLoginRequest, + O: FinishLoginResponse, + kind: MethodKind.Unary, + }, + /** + * ListCredentials returns registered credential ID list + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.ListCredentials + */ + listCredentials: { + name: "ListCredentials", + I: ListCredentialsRequest, + O: ListCredentialsResponse, + kind: MethodKind.Unary, + }, + /** + * UpdateCredential updates registed credential's human readable infomations + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.UpdateCredential + */ + updateCredential: { + name: "UpdateCredential", + I: UpdateCredentialRequest, + O: UpdateCredentialResponse, + kind: MethodKind.Unary, + }, + /** + * DeleteCredential remove registered credential + * + * @generated from rpc dashboard.v1alpha1.WebAuthnService.DeleteCredential + */ + deleteCredential: { + name: "DeleteCredential", + I: DeleteCredentialRequest, + O: DeleteCredentialResponse, + kind: MethodKind.Unary, + }, + } +} as const; + diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_pb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_pb.ts new file mode 100644 index 00000000..7557c566 --- /dev/null +++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/webauthn_pb.ts @@ -0,0 +1,620 @@ +// +//WebAuthn protobuf + +// @generated by protoc-gen-es v1.2.0 with parameter "target=ts" +// @generated from file dashboard/v1alpha1/webauthn.proto (package dashboard.v1alpha1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3, Timestamp } from "@bufbuild/protobuf"; + +/** + * @generated from message dashboard.v1alpha1.BeginRegistrationRequest + */ +export class BeginRegistrationRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.BeginRegistrationRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): BeginRegistrationRequest { + return new BeginRegistrationRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): BeginRegistrationRequest { + return new BeginRegistrationRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BeginRegistrationRequest { + return new BeginRegistrationRequest().fromJsonString(jsonString, options); + } + + static equals(a: BeginRegistrationRequest | PlainMessage | undefined, b: BeginRegistrationRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(BeginRegistrationRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.BeginRegistrationResponse + */ +export class BeginRegistrationResponse extends Message { + /** + * @generated from field: string credential_creation_options = 1; + */ + credentialCreationOptions = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.BeginRegistrationResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "credential_creation_options", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): BeginRegistrationResponse { + return new BeginRegistrationResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): BeginRegistrationResponse { + return new BeginRegistrationResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BeginRegistrationResponse { + return new BeginRegistrationResponse().fromJsonString(jsonString, options); + } + + static equals(a: BeginRegistrationResponse | PlainMessage | undefined, b: BeginRegistrationResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(BeginRegistrationResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.FinishRegistrationRequest + */ +export class FinishRegistrationRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + /** + * @generated from field: string credential_creation_response = 2; + */ + credentialCreationResponse = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.FinishRegistrationRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "credential_creation_response", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): FinishRegistrationRequest { + return new FinishRegistrationRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): FinishRegistrationRequest { + return new FinishRegistrationRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): FinishRegistrationRequest { + return new FinishRegistrationRequest().fromJsonString(jsonString, options); + } + + static equals(a: FinishRegistrationRequest | PlainMessage | undefined, b: FinishRegistrationRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(FinishRegistrationRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.FinishRegistrationResponse + */ +export class FinishRegistrationResponse extends Message { + /** + * @generated from field: string message = 1; + */ + message = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.FinishRegistrationResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): FinishRegistrationResponse { + return new FinishRegistrationResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): FinishRegistrationResponse { + return new FinishRegistrationResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): FinishRegistrationResponse { + return new FinishRegistrationResponse().fromJsonString(jsonString, options); + } + + static equals(a: FinishRegistrationResponse | PlainMessage | undefined, b: FinishRegistrationResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(FinishRegistrationResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.BeginLoginRequest + */ +export class BeginLoginRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.BeginLoginRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): BeginLoginRequest { + return new BeginLoginRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): BeginLoginRequest { + return new BeginLoginRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BeginLoginRequest { + return new BeginLoginRequest().fromJsonString(jsonString, options); + } + + static equals(a: BeginLoginRequest | PlainMessage | undefined, b: BeginLoginRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(BeginLoginRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.BeginLoginResponse + */ +export class BeginLoginResponse extends Message { + /** + * @generated from field: string credential_request_options = 1; + */ + credentialRequestOptions = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.BeginLoginResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "credential_request_options", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): BeginLoginResponse { + return new BeginLoginResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): BeginLoginResponse { + return new BeginLoginResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BeginLoginResponse { + return new BeginLoginResponse().fromJsonString(jsonString, options); + } + + static equals(a: BeginLoginResponse | PlainMessage | undefined, b: BeginLoginResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(BeginLoginResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.FinishLoginRequest + */ +export class FinishLoginRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + /** + * @generated from field: string credential_request_result = 2; + */ + credentialRequestResult = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.FinishLoginRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "credential_request_result", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): FinishLoginRequest { + return new FinishLoginRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): FinishLoginRequest { + return new FinishLoginRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): FinishLoginRequest { + return new FinishLoginRequest().fromJsonString(jsonString, options); + } + + static equals(a: FinishLoginRequest | PlainMessage | undefined, b: FinishLoginRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(FinishLoginRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.FinishLoginResponse + */ +export class FinishLoginResponse extends Message { + /** + * @generated from field: string message = 1; + */ + message = ""; + + /** + * @generated from field: google.protobuf.Timestamp expire_at = 2; + */ + expireAt?: Timestamp; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.FinishLoginResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "expire_at", kind: "message", T: Timestamp }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): FinishLoginResponse { + return new FinishLoginResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): FinishLoginResponse { + return new FinishLoginResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): FinishLoginResponse { + return new FinishLoginResponse().fromJsonString(jsonString, options); + } + + static equals(a: FinishLoginResponse | PlainMessage | undefined, b: FinishLoginResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(FinishLoginResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.ListCredentialsRequest + */ +export class ListCredentialsRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.ListCredentialsRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListCredentialsRequest { + return new ListCredentialsRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListCredentialsRequest { + return new ListCredentialsRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListCredentialsRequest { + return new ListCredentialsRequest().fromJsonString(jsonString, options); + } + + static equals(a: ListCredentialsRequest | PlainMessage | undefined, b: ListCredentialsRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(ListCredentialsRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.ListCredentialsResponse + */ +export class ListCredentialsResponse extends Message { + /** + * @generated from field: string message = 1; + */ + message = ""; + + /** + * @generated from field: repeated dashboard.v1alpha1.Credential credentials = 2; + */ + credentials: Credential[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.ListCredentialsResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "credentials", kind: "message", T: Credential, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ListCredentialsResponse { + return new ListCredentialsResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ListCredentialsResponse { + return new ListCredentialsResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ListCredentialsResponse { + return new ListCredentialsResponse().fromJsonString(jsonString, options); + } + + static equals(a: ListCredentialsResponse | PlainMessage | undefined, b: ListCredentialsResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(ListCredentialsResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.Credential + */ +export class Credential extends Message { + /** + * @generated from field: string id = 1; + */ + id = ""; + + /** + * @generated from field: string display_name = 2; + */ + displayName = ""; + + /** + * @generated from field: google.protobuf.Timestamp timestamp = 3; + */ + timestamp?: Timestamp; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.Credential"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "display_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "timestamp", kind: "message", T: Timestamp }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): Credential { + return new Credential().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Credential { + return new Credential().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Credential { + return new Credential().fromJsonString(jsonString, options); + } + + static equals(a: Credential | PlainMessage | undefined, b: Credential | PlainMessage | undefined): boolean { + return proto3.util.equals(Credential, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.DeleteCredentialRequest + */ +export class DeleteCredentialRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + /** + * @generated from field: string cred_id = 2; + */ + credId = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.DeleteCredentialRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "cred_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): DeleteCredentialRequest { + return new DeleteCredentialRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): DeleteCredentialRequest { + return new DeleteCredentialRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): DeleteCredentialRequest { + return new DeleteCredentialRequest().fromJsonString(jsonString, options); + } + + static equals(a: DeleteCredentialRequest | PlainMessage | undefined, b: DeleteCredentialRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(DeleteCredentialRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.DeleteCredentialResponse + */ +export class DeleteCredentialResponse extends Message { + /** + * @generated from field: string message = 1; + */ + message = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.DeleteCredentialResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): DeleteCredentialResponse { + return new DeleteCredentialResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): DeleteCredentialResponse { + return new DeleteCredentialResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): DeleteCredentialResponse { + return new DeleteCredentialResponse().fromJsonString(jsonString, options); + } + + static equals(a: DeleteCredentialResponse | PlainMessage | undefined, b: DeleteCredentialResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(DeleteCredentialResponse, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.UpdateCredentialRequest + */ +export class UpdateCredentialRequest extends Message { + /** + * @generated from field: string user_name = 1; + */ + userName = ""; + + /** + * @generated from field: string cred_id = 2; + */ + credId = ""; + + /** + * @generated from field: string cred_display_name = 3; + */ + credDisplayName = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.UpdateCredentialRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "cred_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "cred_display_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): UpdateCredentialRequest { + return new UpdateCredentialRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): UpdateCredentialRequest { + return new UpdateCredentialRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UpdateCredentialRequest { + return new UpdateCredentialRequest().fromJsonString(jsonString, options); + } + + static equals(a: UpdateCredentialRequest | PlainMessage | undefined, b: UpdateCredentialRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(UpdateCredentialRequest, a, b); + } +} + +/** + * @generated from message dashboard.v1alpha1.UpdateCredentialResponse + */ +export class UpdateCredentialResponse extends Message { + /** + * @generated from field: string message = 1; + */ + message = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "dashboard.v1alpha1.UpdateCredentialResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): UpdateCredentialResponse { + return new UpdateCredentialResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): UpdateCredentialResponse { + return new UpdateCredentialResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UpdateCredentialResponse { + return new UpdateCredentialResponse().fromJsonString(jsonString, options); + } + + static equals(a: UpdateCredentialResponse | PlainMessage | undefined, b: UpdateCredentialResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(UpdateCredentialResponse, a, b); + } +} + diff --git a/web/dashboard-ui/src/services/DashboardServices.ts b/web/dashboard-ui/src/services/DashboardServices.ts index 3375db9a..a1e70f46 100644 --- a/web/dashboard-ui/src/services/DashboardServices.ts +++ b/web/dashboard-ui/src/services/DashboardServices.ts @@ -4,6 +4,7 @@ import { useMemo } from "react"; import { AuthService } from "../proto/gen/dashboard/v1alpha1/auth_service_connectweb"; import { TemplateService } from "../proto/gen/dashboard/v1alpha1/template_service_connectweb"; import { UserService } from "../proto/gen/dashboard/v1alpha1/user_service_connectweb"; +import { WebAuthnService } from "../proto/gen/dashboard/v1alpha1/webauthn_connectweb"; import { WorkspaceService } from "../proto/gen/dashboard/v1alpha1/workspace_service_connectweb"; const transportX = createConnectTransport({ @@ -23,7 +24,9 @@ export function useTemplateService() { export function useUserService() { return useMemo(() => createPromiseClient(UserService, transport), [UserService]); } - export function useWorkspaceService() { return useMemo(() => createPromiseClient(WorkspaceService, transport), [WorkspaceService]); +} +export function useWebAuthnService() { + return useMemo(() => createPromiseClient(WebAuthnService, transport), [WebAuthnService]); } \ No newline at end of file diff --git a/web/dashboard-ui/src/views/atoms/EditableTypography.tsx b/web/dashboard-ui/src/views/atoms/EditableTypography.tsx new file mode 100644 index 00000000..f436ecad --- /dev/null +++ b/web/dashboard-ui/src/views/atoms/EditableTypography.tsx @@ -0,0 +1,63 @@ +import { Check, Close, Edit } from "@mui/icons-material"; +import { + IconButton, + InputBase, InputBaseProps, + Stack, + Tooltip +} from "@mui/material"; +import React, { useState } from "react"; + +export type EditableTypographyProps = + InputBaseProps + & { children: string, onSave: (inputData: string) => void, showAlways?: boolean }; + +export const EditableTypography: React.FC = ({ children, onSave, showAlways, ...props }) => { + + const [showEditIcon, setShowEditIcon] = useState(showAlways); + const [editting, setEditing] = useState(false); + const [inputData, setInputData] = useState(children); + + const editIcon = ( + + { setEditing(true) }}> + + + + ); + const editingIcons = ( + + + { onSave(inputData); setEditing(false) }}> + + + + + { + setInputData(children); setEditing(false) + }}> + + + + + ); + + + return ( + { setShowEditIcon(showAlways || true) }} + onMouseLeave={() => { setShowEditIcon(showAlways || false) }} + onBlur={(e) => { setInputData(e.currentTarget.value) }} + sx={{ borderBottom: editting ? 1 : 0 }} + {...props} + />) +} diff --git a/web/dashboard-ui/src/views/atoms/EllipsisTypography.tsx b/web/dashboard-ui/src/views/atoms/EllipsisTypography.tsx new file mode 100644 index 00000000..d76e65e2 --- /dev/null +++ b/web/dashboard-ui/src/views/atoms/EllipsisTypography.tsx @@ -0,0 +1,32 @@ +import { Tooltip, TooltipProps, Typography, TypographyProps } from "@mui/material"; +import React from "react"; + + + +export type EllipsisTypographyProps = + Omit + & { children: string, placement?: TooltipProps["placement"] }; + +export const EllipsisTypography: React.FC = ({ children, placement }) => { + + const [isOverflow, setIsOverflow] = React.useState(false); + const paragraph = React.useRef(null); + + React.useEffect(() => { + const pElement = paragraph.current; + if (pElement) { + setIsOverflow(Boolean(pElement.offsetWidth < pElement.scrollWidth)); + } + }, [paragraph]); + + const title = children + + // return isOverflow ? ( {typoglaphy} ) : typoglaphy + return isOverflow ? ( + + {children} + + ) : {children} +} diff --git a/web/dashboard-ui/src/views/organisms/AuthenticatorManageDialog.tsx b/web/dashboard-ui/src/views/organisms/AuthenticatorManageDialog.tsx new file mode 100644 index 00000000..2e072901 --- /dev/null +++ b/web/dashboard-ui/src/views/organisms/AuthenticatorManageDialog.tsx @@ -0,0 +1,209 @@ +import { Delete, VerifiedOutlined } from "@mui/icons-material"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + Grid, + IconButton, + Tooltip, + Typography, + useMediaQuery, useTheme +} from "@mui/material"; +import Box from '@mui/material/Box'; +import { useSnackbar } from 'notistack'; +import { useEffect, useState } from "react"; +import { base64url } from '../../components/Base64'; +import { DialogContext } from "../../components/ContextProvider"; +import { User } from "../../proto/gen/dashboard/v1alpha1/user_pb"; +import { Credential } from "../../proto/gen/dashboard/v1alpha1/webauthn_pb"; +import { useWebAuthnService } from '../../services/DashboardServices'; +import { EditableTypography } from "../atoms/EditableTypography"; +import { EllipsisTypography } from "../atoms/EllipsisTypography"; +/** + * view + */ + +export const AuthenticatorManageDialog: React.VFC<{ onClose: () => void, user: User }> = ({ onClose, user }) => { + console.log('AuthenticatorManageDialog'); + const webauthnService = useWebAuthnService(); + const { enqueueSnackbar } = useSnackbar(); + + const [credentials, setCredentials] = useState([]); + + const registerdCredId = localStorage.getItem(`credId`) + const isRegistered = Boolean(registerdCredId && credentials.map(c => c.id).includes(registerdCredId!)); + + const [isWebAuthnAvailable, setIsWebAuthnAvailable] = useState(false); + + const checkWebAuthnAvailable = () => { + if (window.PublicKeyCredential) { + PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() + .then(uvpaa => { setIsWebAuthnAvailable(uvpaa) }); + } + } + useEffect(() => { checkWebAuthnAvailable() }, []); + + console.log("credId", registerdCredId, "isRegistered", isRegistered, "isWebAuthnAvailable", isWebAuthnAvailable); + + /** + * listCredentials + */ + const listCredentials = async () => { + console.log("listCredentials"); + try { + const resp = await webauthnService.listCredentials({ userName: user.name }); + setCredentials(resp.credentials); + } + catch (error) { + handleError(error); + } + } + useEffect(() => { listCredentials() }, []); + + /** + * registerNewAuthenticator + */ + const registerNewAuthenticator = async () => { + try { + const resp = await webauthnService.beginRegistration({ userName: user.name }); + const options = JSON.parse(resp.credentialCreationOptions); + + const opt: CredentialCreationOptions = JSON.parse(JSON.stringify(options)); + if (options.publicKey?.user.id) { + opt.publicKey!.user.id = base64url.decode(options.publicKey?.user.id); + } + if (options.publicKey?.challenge) { + opt.publicKey!.challenge = base64url.decode(options.publicKey?.challenge); + } + + // Credential is allowed to access only id and type so use any. + const cred: any = await navigator.credentials.create(opt); + if (cred === null) { + console.log("cred is null"); + throw Error('credential is null'); + } + + const credential = { + id: cred.id, + rawId: base64url.encode(cred.rawId), + type: cred.type, + response: { + clientDataJSON: base64url.encode(cred.response.clientDataJSON), + attestationObject: base64url.encode(cred.response.attestationObject) + } + }; + localStorage.setItem(`credId`, credential.rawId); + + const finResp = await webauthnService.finishRegistration({ userName: user.name, credentialCreationResponse: JSON.stringify(credential) }); + enqueueSnackbar(finResp.message, { variant: 'success' }); + listCredentials(); + } + catch (error) { + handleError(error); + } + } + + /** + * removeCredentials + */ + const removeCredentials = async (id: string) => { + console.log("removeCredentials"); + if (!confirm("Are you sure to REMOVE?\nID: " + id)) { return } + try { + const resp = await webauthnService.deleteCredential({ userName: user.name, credId: id }); + enqueueSnackbar(resp.message, { variant: 'success' }); + listCredentials(); + if (id === registerdCredId) { + localStorage.removeItem(`credId`); + } + } + catch (error) { + handleError(error); + } + } + + /** + * updateCredentialName + */ + const updateCredentialName = async (id: string, name: string) => { + try { + const resp = await webauthnService.updateCredential({ userName: user.name, credId: id, credDisplayName: name }); + enqueueSnackbar(resp.message, { variant: 'success' }); + listCredentials(); + } + catch (error) { + handleError(error); + } + } + + /** + * error handler + */ + const handleError = (error: any) => { + console.log(error); + const msg = error?.message; + error instanceof DOMException || msg && enqueueSnackbar(msg, { variant: 'error' }); + } + + const theme = useTheme(); + const sm = useMediaQuery(theme.breakpoints.up('sm'), { noSsr: true }); + + return ( + + WebAuthn Credentials + + + {credentials.length === 0 + ? No credentials + : + + Created + Credential ID & Name + + + {credentials.map((field, index) => { + return ( + <> + {sm && + {registerdCredId === field.id && + + + + || undefined} + } + + {field.timestamp?.toDate().toLocaleDateString()} + {field.timestamp?.toDate().toLocaleTimeString()} + + + {field.id} + { updateCredentialName(field.id, input) }}>{field.displayName} + + < Grid item xs={1} sx={{ m: 'auto', textAlign: 'center' }}> + { removeCredentials(field.id) }}> + + + ) + })} + } + + + + + {!isRegistered && isWebAuthnAvailable + ? + : undefined} + + + ); +}; + +/** + * Context + */ +export const AuthenticatorManageDialogContext = DialogContext<{ user: User }>( + props => ()); diff --git a/web/dashboard-ui/src/views/organisms/RoleChangeDialog.tsx b/web/dashboard-ui/src/views/organisms/RoleChangeDialog.tsx index e42233e7..d429b98d 100644 --- a/web/dashboard-ui/src/views/organisms/RoleChangeDialog.tsx +++ b/web/dashboard-ui/src/views/organisms/RoleChangeDialog.tsx @@ -3,8 +3,9 @@ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormHelperText, Grid, IconButton, Stack, TextField, Typography } from "@mui/material"; import React from "react"; -import { useFieldArray, useForm, UseFormRegisterReturn } from "react-hook-form"; +import { UseFormRegisterReturn, useFieldArray, useForm } from "react-hook-form"; import { DialogContext } from "../../components/ContextProvider"; +import { useLogin } from "../../components/LoginProvider"; import { User } from "../../proto/gen/dashboard/v1alpha1/user_pb"; import { FormSelectableChip } from "../atoms/SelectableChips"; import { TextFieldLabel } from "../atoms/TextFieldLabel"; @@ -25,6 +26,7 @@ interface Inputs { export const RoleChangeDialog: React.VFC<{ onClose: () => void, user: User }> = ({ onClose, user }) => { console.log('RoleChangeDialog'); const hooks = useUserModule(); + const { refreshUserInfo } = useLogin(); const { register, handleSubmit, control, formState: { errors, defaultValues } } = useForm({ defaultValues: { @@ -50,7 +52,7 @@ export const RoleChangeDialog: React.VFC<{ onClose: () => void, user: User }> = Change Role -
{ + { console.log(inp) let protoRoles = inp.roles.filter((v) => { return v.name !== "" }).map((v) => { return v.name }) console.log(protoRoles) @@ -61,8 +63,9 @@ export const RoleChangeDialog: React.VFC<{ onClose: () => void, user: User }> = }) protoRoles = [...new Set(protoRoles)]; // remove duplicates console.log("protoRoles", protoRoles) - hooks.updateRole(user.name, protoRoles) - .then(() => onClose()); + await hooks.updateRole(user.name, protoRoles); + await refreshUserInfo(); + onClose(); })} autoComplete="new-password"> diff --git a/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx b/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx index b8c63dea..b3dbf695 100644 --- a/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx +++ b/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx @@ -10,6 +10,7 @@ import { import React, { useEffect } from "react"; import { UseFormRegisterReturn, useFieldArray, useForm } from "react-hook-form"; import { DialogContext } from "../../components/ContextProvider"; +import { useLogin } from "../../components/LoginProvider"; import { Template } from "../../proto/gen/dashboard/v1alpha1/template_pb"; import { User, UserAddon } from "../../proto/gen/dashboard/v1alpha1/user_pb"; import { TextFieldLabel } from "../atoms/TextFieldLabel"; @@ -32,6 +33,7 @@ interface Inputs { export const UserAddonChangeDialog: React.FC<{ onClose: () => void, user: User }> = ({ onClose, user }) => { console.log('UserAddonChangeDialog'); const hooks = useUserModule(); + const { refreshUserInfo } = useLogin(); const { register, handleSubmit, watch, control, formState: { errors } } = useForm({ defaultValues: {} @@ -55,7 +57,7 @@ export const UserAddonChangeDialog: React.FC<{ onClose: () => void, user: User } Change UserAddons - { + { console.log(inp) const userAddons = inp.addons.filter(v => v.enable) .map((inpAddon) => { @@ -69,8 +71,9 @@ export const UserAddonChangeDialog: React.FC<{ onClose: () => void, user: User } console.log("protoUserAddons", protoUserAddons) // call API - hooks.updateAddons(user.name, protoUserAddons) - .then(() => onClose()); + await hooks.updateAddons(user.name, protoUserAddons); + await refreshUserInfo(); + onClose(); })} autoComplete="new-password"> diff --git a/web/dashboard-ui/src/views/pages/SignIn.tsx b/web/dashboard-ui/src/views/pages/SignIn.tsx index 67e1e7a2..597c552a 100644 --- a/web/dashboard-ui/src/views/pages/SignIn.tsx +++ b/web/dashboard-ui/src/views/pages/SignIn.tsx @@ -1,6 +1,7 @@ +import { Fingerprint, Person } from '@mui/icons-material'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import { Avatar, Button, Container, CssBaseline, Stack, TextField, Typography } from '@mui/material'; -import React from 'react'; +import { Avatar, Button, Container, CssBaseline, Divider, InputAdornment, Stack, TextField, Typography } from '@mui/material'; +import React, { useEffect } from 'react'; import { UseFormRegisterReturn, useForm } from 'react-hook-form'; import { useLocation, useNavigate } from 'react-router-dom'; import { useLogin } from '../../components/LoginProvider'; @@ -47,7 +48,7 @@ export function SignIn() { const SignInContent: React.VFC = () => { console.log('SignIn'); const { register, handleSubmit, formState: { errors } } = useForm(); - const { login, loginUser } = useLogin(); + const { login, loginUser, loginWithWebAuthn } = useLogin(); const navigate = useNavigate(); const location = useLocation(); @@ -77,16 +78,38 @@ const SignInContent: React.VFC = () => { /** * submit - */ + */ + const [usePasswordLogin, setUsePasswordLogin] = React.useState(false); + const onSignIn = async (data: Inputs) => { console.log('onSignIn'); - const { requirePasswordUpdate } = await login(data.username, data.password); - if (requirePasswordUpdate) { - passwordChangeDialogDispach(true); + if (usePasswordLogin) { + const { requirePasswordUpdate } = await login(data.username, data.password); + if (requirePasswordUpdate) { + passwordChangeDialogDispach(true); + } + } else { + await loginWithWebAuthn(data.username); } redirect(); } + const checkWebAuthnAvailable = () => { + if (window.PublicKeyCredential) { + PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() + .then(uvpaa => { + if (uvpaa && localStorage.getItem(`credId`)) { + setUsePasswordLogin(false); + } else { + setUsePasswordLogin(true); + } + }); + } else { + setUsePasswordLogin(true); + } + } + useEffect(() => { checkWebAuthnAvailable() }, []); + return ( @@ -96,6 +119,7 @@ const SignInContent: React.VFC = () => { cosmo-dashboard ) }} {...registerMui(register("username", { required: { value: true, message: "Required" }, pattern: { @@ -107,15 +131,19 @@ const SignInContent: React.VFC = () => { error={Boolean(errors.username)} helperText={(errors.username && errors.username.message)} /> - - + {usePasswordLogin && + } + + + {usePasswordLogin || + } ); diff --git a/web/dashboard-ui/src/views/templates/PageTemplate.tsx b/web/dashboard-ui/src/views/templates/PageTemplate.tsx index 817c310d..c083fd9f 100644 --- a/web/dashboard-ui/src/views/templates/PageTemplate.tsx +++ b/web/dashboard-ui/src/views/templates/PageTemplate.tsx @@ -1,4 +1,4 @@ -import { AccountCircle, Badge, ExitToApp, LockOutlined, Menu as MenuIcon, ReportProblem, SupervisorAccountTwoTone, VpnKey, WebTwoTone } from "@mui/icons-material"; +import { AccountCircle, Badge, ExitToApp, FingerprintTwoTone, LockOutlined, Menu as MenuIcon, ReportProblem, SupervisorAccountTwoTone, VpnKey, WebTwoTone } from "@mui/icons-material"; import { Alert, Box, Button, Chip, @@ -15,6 +15,7 @@ import { Link as RouterLink } from "react-router-dom"; import { useLogin } from "../../components/LoginProvider"; import logo from "../../logo-with-name-small.png"; import { NameAvatar } from "../atoms/NameAvatar"; +import { AuthenticatorManageDialogContext } from "../organisms/AuthenticatorManageDialog"; import { PasswordChangeDialogContext } from "../organisms/PasswordChangeDialog"; import { UserInfoDialogContext } from "../organisms/UserActionDialog"; import { UserAddonChangeDialogContext } from "../organisms/UserAddonsChangeDialog"; @@ -49,6 +50,7 @@ interface PageTemplateProps { export const PageTemplate: React.FC> = ({ children, title, }) => { const { loginUser, logout } = useLogin(); + const authenticatorManagerDialogDispatch = AuthenticatorManageDialogContext.useDispatch(); const passwordChangeDialogDispach = PasswordChangeDialogContext.useDispatch(); const userNameChangeDialogDispach = UserNameChangeDialogContext.useDispatch(); const userAddonChangeDialogDispatch = UserAddonChangeDialogContext.useDispatch(); @@ -57,6 +59,12 @@ export const PageTemplate: React.FC> const isSignIn = Boolean(loginUser); const canChangePassword = Boolean(loginUser?.authType === 'password-secret'); + const manageAuthenticators = () => { + console.log('manageAuthenticators'); + authenticatorManagerDialogDispatch(true, { user: loginUser! }); + setAnchorEl(null); + } + const changeUserName = () => { console.log('changeUserName'); userNameChangeDialogDispach(true, { user: loginUser! }); @@ -159,17 +167,21 @@ export const PageTemplate: React.FC> - {isSignIn && changeUserName()}> - - Change user name... + {isSignIn && manageAuthenticators()}> + + Manage WebAuthn Credentials... } {isSignIn && canChangePassword && changePassword()}> - Change password... + Change Password... + } + {isSignIn && changeUserName()}> + + Change Name... } {isSignIn && isAdmin && changeAddons()}> - Change addons... + Change Addons... } {isSignIn && logout()}> diff --git a/web/dashboard-ui/yarn.lock b/web/dashboard-ui/yarn.lock index 5f1fb6c0..ad467829 100644 --- a/web/dashboard-ui/yarn.lock +++ b/web/dashboard-ui/yarn.lock @@ -1065,6 +1065,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"