diff --git a/cmd/devinit/core.go b/cmd/devinit/core.go index 036112061..bfa5a1ae6 100644 --- a/cmd/devinit/core.go +++ b/cmd/devinit/core.go @@ -55,6 +55,10 @@ func (c *Config) makeCore() error { "schemes": []string{"https"}, }, }, + "zanzibar": map[string]interface{}{ + "token": c.zanzibarToken, + "prefix": c.zanzibarPrefix, + }, } yamlBytes, err := yaml.Marshal(coreConfig) diff --git a/cmd/devinit/main.go b/cmd/devinit/main.go index 6683673cf..304bb97ae 100644 --- a/cmd/devinit/main.go +++ b/cmd/devinit/main.go @@ -38,6 +38,7 @@ var ( CoreFormsApiDir string CoreAuthnzApiDir string CoreAuthnzBouncerDir string + ZanzibarDir string LoginDir string RedisDir string PostgresDir string @@ -166,6 +167,8 @@ type Config struct { rootCaKeyPath string rootCaPath string rootDir string + zanzibarToken string + zanzibarPrefix string } func Init() error { @@ -268,6 +271,7 @@ func createConfig() (*Config, error) { config.makePostgresInit, config.makePostgres, config.makeHydraInit, + config.makeZanzibarConfig, } for _, f := range funcs { @@ -456,6 +460,7 @@ func init() { OIDCDir = path.Join(CredsDir, "oidc") IDPDir = path.Join(CredsDir, "nrc_idp") CoreDir = path.Join(CredsDir, "core") + ZanzibarDir = path.Join(CredsDir, "zanzibar") LoginDir = path.Join(CoreDir, "login") CoreAppFrontendDir = path.Join(CoreDir, "app_frontend") CoreAdminFrontendDir = path.Join(CoreDir, "admin_frontend") diff --git a/cmd/devinit/postgres.go b/cmd/devinit/postgres.go index c1e29620a..7573ddaa6 100644 --- a/cmd/devinit/postgres.go +++ b/cmd/devinit/postgres.go @@ -8,6 +8,7 @@ import ( func (c *Config) makePostgres() error { var err error + c.postgresRootPassword, err = getOrCreateRandomSecretStr(32, PostgresDir, "password") if err != nil { return err diff --git a/cmd/devinit/utils.go b/cmd/devinit/utils.go index 478f9d661..09da6b23e 100644 --- a/cmd/devinit/utils.go +++ b/cmd/devinit/utils.go @@ -15,6 +15,7 @@ import ( "os" "path" "path/filepath" + "strings" "time" ) @@ -207,7 +208,12 @@ func genServerCert( func getOrCreateRandomSecretStr(length int, filePaths ...string) (string, error) { secretBytes, err := getOrCreateRandomSecret(length, filePaths...) - return string(secretBytes), err + if err != nil { + return "", err + } + secretStr := string(secretBytes) + secretStr = strings.Trim(secretStr, "\n") + return secretStr, nil } func getOrCreateRandomSecret(length int, filePaths ...string) ([]byte, error) { diff --git a/cmd/devinit/zanzibar.go b/cmd/devinit/zanzibar.go new file mode 100644 index 000000000..4c76e105f --- /dev/null +++ b/cmd/devinit/zanzibar.go @@ -0,0 +1,17 @@ +package devinit + +func (c *Config) makeZanzibarConfig() error { + + var err error + c.zanzibarToken, err = getOrCreateRandomSecretStr(32, ZanzibarDir, "token") + if err != nil { + return err + } + + c.zanzibarPrefix, err = getOrCreateRandomSecretStr(32, ZanzibarDir, "prefix") + if err != nil { + return err + } + + return nil +} diff --git a/cmd/serve.go b/cmd/serve.go index 3981bab51..479413b31 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -6,6 +6,7 @@ import ( "github.com/nrc-no/core/pkg/logging" "github.com/nrc-no/core/pkg/server/options" "github.com/nrc-no/core/pkg/store" + client2 "github.com/nrc-no/core/pkg/zanzibar" "github.com/spf13/viper" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -20,6 +21,7 @@ var osSignal = make(chan os.Signal) var doneSignal = make(chan struct{}) var ctx = context.Background() var factory store.Factory +var zanzibarClient client2.ZanzibarClient // serveCmd represents the serve command var serveCmd = &cobra.Command{ @@ -81,6 +83,13 @@ func initStoreFactory() error { return nil } +func initZanzibarClient() { + zanzibarClient = client2.NewZanzibarClient(client2.ZanzibarClientConfig{ + Token: coreOptions.Zanzibar.Token, + Prefix: coreOptions.Zanzibar.Prefix, + }) +} + func init() { rootCmd.AddCommand(serveCmd) } diff --git a/cmd/serve_all.go b/cmd/serve_all.go index d10d93e68..af59c3ecc 100644 --- a/cmd/serve_all.go +++ b/cmd/serve_all.go @@ -16,10 +16,14 @@ var serveAllCmd = &cobra.Command{ if err := initStoreFactory(); err != nil { return err } + + initZanzibarClient() + if err := serveFormsApi(ctx, formsapiserver.Options{ - ServerOptions: coreOptions.Serve.FormsApi, - StoreFactory: factory, + ServerOptions: coreOptions.Serve.FormsApi, + StoreFactory: factory, + ZanzibarClient: zanzibarClient, }); err != nil { return err } diff --git a/cmd/serve_forms_api.go b/cmd/serve_forms_api.go index 3f2e6e411..7e1faba1f 100644 --- a/cmd/serve_forms_api.go +++ b/cmd/serve_forms_api.go @@ -14,10 +14,14 @@ var servePublicCmd = &cobra.Command{ if err := initStoreFactory(); err != nil { return err } + + initZanzibarClient() + if err := serveFormsApi(ctx, formsapiserver.Options{ ServerOptions: coreOptions.Serve.FormsApi, StoreFactory: factory, + ZanzibarClient: zanzibarClient, }); err != nil { return err } diff --git a/frontend/packages/core-auth/src/components/AuthZWrapper.tsx b/frontend/packages/core-auth/src/components/AuthZWrapper.tsx new file mode 100644 index 000000000..24c545d46 --- /dev/null +++ b/frontend/packages/core-auth/src/components/AuthZWrapper.tsx @@ -0,0 +1,189 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { getLogger, LogLevelDesc } from 'loglevel'; + +import Browser from '../types/browser'; +import exchangeCodeAsync from '../utils/exchangeCodeAsync'; +import useDiscovery from '../hooks/useDiscovery'; +import useAuthRequest from '../hooks/useAuthRequest'; +import { + AuthWrapperProps, + CodeChallengeMethod, + ResponseType, +} from '../types/types'; +import { TokenResponse } from '../types/response'; +import { getJSONFromSessionStorage } from '../utils/getJSONFromSessionStorage'; + +const log = getLogger('AuthWrapper'); +const loglevel = process?.env?.LOG_LEVEL as LogLevelDesc; +log.setLevel(loglevel || log.levels.INFO); + +const AuthWrapper: React.FC = ({ + children, + scopes = [], + clientId, + issuer, + redirectUri, + customLoginComponent, + handleLoginErr = log.debug, + onTokenChange, + injectToken = 'access_token', +}) => { + const browser = useMemo(() => new Browser(), []); + + browser.maybeCompleteAuthSession(); + + const discovery = useDiscovery(issuer); + + const [tokenResponse, setTokenResponse] = useState( + TokenResponse.createTokenResponse(getJSONFromSessionStorage(injectToken)), + ); + + const [isLoggedIn, setIsLoggedIn] = useState(false); + + const [request, response, promptAsync] = useAuthRequest( + { + clientId, + usePKCE: true, + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + scopes, + redirectUri, + }, + discovery, + browser, + ); + + // Make initial token request + // trigger login automatically + useEffect(() => { + (async () => { + if ( + !discovery || + request?.codeVerifier == null || + !response || + response?.type !== 'success' + ) { + return; + } + + const exchangeConfig = { + code: response.params.code, + clientId, + redirectUri, + extraParams: { + code_verifier: request?.codeVerifier, + }, + }; + + try { + const tr = await exchangeCodeAsync(exchangeConfig, discovery); + setTokenResponse(tr); + } catch { + setTokenResponse(undefined); + } + })(); + }, [request?.codeVerifier, response, discovery]); + + // Each second, check if token is fresh + // If not, refresh the token + const refreshTokenInterval = React.useRef(null); + React.useEffect(() => { + const refreshToken = async () => { + if (!discovery) return; + if (!tokenResponse) return; + if (!tokenResponse.shouldRefresh()) return; + + const refreshConfig = { + clientId, + scopes, + extraParams: {}, + refreshToken: tokenResponse.refreshToken, + }; + + try { + const resp = await TokenResponse.refreshAsync(refreshConfig, discovery); + setTokenResponse(resp); + } catch (err) { + setTokenResponse(undefined); + } + }; + + if (refreshTokenInterval.current) + window.clearInterval(refreshTokenInterval.current); + + if ( + tokenResponse && + tokenResponse.expiresIn && + tokenResponse.expiresIn > 0 + ) { + refreshTokenInterval.current = window.setInterval(refreshToken, 1000); + } + + return () => { + if (refreshTokenInterval.current) + clearInterval(refreshTokenInterval.current); + }; + }, [ + tokenResponse?.refreshToken, + tokenResponse?.expiresIn, + JSON.stringify(discovery), + ]); + + // Run onTokenChange callback + // store token in session storage or remove it when undefined + useEffect(() => { + if (!tokenResponse) { + onTokenChange(''); + sessionStorage.removeItem(injectToken); + } else { + const token = (() => { + switch (injectToken) { + case 'access_token': + return tokenResponse?.accessToken ?? ''; + case 'id_token': + return tokenResponse?.idToken ?? ''; + default: + return ''; + } + })(); + onTokenChange(token); + sessionStorage.setItem(injectToken, JSON.stringify(tokenResponse)); + } + }, [JSON.stringify(tokenResponse)]); + + // Update logged in status accordingly + useEffect(() => { + if (tokenResponse) { + if (!isLoggedIn) { + setIsLoggedIn(true); + } + } else if (isLoggedIn) { + setIsLoggedIn(false); + } + }, [JSON.stringify(tokenResponse), isLoggedIn]); + + // trigger login manually, by clicking + const handleLogin = useCallback(() => { + promptAsync().catch((err) => { + handleLoginErr(err); + }); + }, [discovery, request, promptAsync]); + + if (!isLoggedIn) { + return ( + <> + {customLoginComponent ? ( + customLoginComponent({ login: handleLogin }) + ) : ( + + )} + + ); + } + + return <>{children}; +}; + +export default AuthWrapper; diff --git a/go.mod b/go.mod index f4078e5b9..b3a78dfa6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/nrc-no/core go 1.16 require ( + github.com/authzed/authzed-go v0.6.0 + github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect github.com/coreos/go-oidc/v3 v3.0.0 diff --git a/go.sum b/go.sum index a68f5ce24..a98bde9eb 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAV cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= 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= @@ -66,6 +67,11 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/authzed/authzed-go v0.6.0 h1:Dcmn+juq2FPNLcIaRPoQr1Hsj0ItTgn7r9j40u2gTLU= +github.com/authzed/authzed-go v0.6.0/go.mod h1:QiymVenghya1D8dgs2pjG7/Cn3jE2TYY4+hHL0xxrgo= +github.com/authzed/grpcutil v0.0.0-20210913124023-cad23ae5a9e8/go.mod h1:HwO/KbRU3fWXEYHE96kvXnwxzi97tkXD1hfi5UaZ71Y= +github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 h1:sZM7XzdyuLyxj7pC/g7uX+XAqZ7m6NMxZzuQRovgBPw= +github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5/go.mod h1:rqjY3zyK/YP7NID9+B2BdIRRkvnK+cdf9/qya/zaFZE= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -74,6 +80,8 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= @@ -119,6 +127,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7 h1:qcZcULcd/abmQg6dwigimCNEyi4gg31M/xaciQlDml8= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -137,7 +147,9 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 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-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -268,6 +280,8 @@ github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPh github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= 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= @@ -356,7 +370,12 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 h1:rgxjzoDmDXw5q8HONgyHhBas4to0/XWRo/gPpJhsUNQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0/go.mod h1:qrJPVzv9YlhsrxJc3P/Q85nr0w1lIRikTl4JlhdDH5w= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -386,6 +405,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -457,6 +477,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b h1:FQ7+9fxhyp82ks9vAuyPzG0/vVbWwMwLJ+P6yJI5FN8= github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b/go.mod h1:HMcgvsgd0Fjj4XXDkbjdmlbI505rUPBs6WBMYg2pXks= +github.com/jzelinskie/stringz v0.0.0-20210414224931-d6a8ce844a70 h1:thTca5Eyouk5CEcJ75Cbw9CSAGE7TAc6rIi+WgHWpOE= +github.com/jzelinskie/stringz v0.0.0-20210414224931-d6a8ce844a70/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -466,6 +488,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -486,6 +509,7 @@ github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJV github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= github.com/looplab/fsm v0.3.0 h1:kIgNS3Yyud1tyxhG8kDqh853B7QqwnlWdgL3TD2s3Sw= github.com/looplab/fsm v0.3.0/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -542,6 +566,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/ory/hydra-client-go v1.10.6 h1:w+uPgePbmztyLzwxWxOF89E/AG6wZuWTteHILn57BoQ= @@ -591,6 +616,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/snabb/isoweek v1.0.1 h1:B4IsN2GU8lCNVkaUUgOzaVpPkKC2DdY9zcnxz5yc0qg= github.com/snabb/isoweek v1.0.1/go.mod h1:CAijAxH7NMgjqGc9baHMDE4sTHMt4B/f6X/XLiEE1iA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -737,6 +763,7 @@ 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.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -786,6 +813,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -883,7 +912,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA= golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1037,6 +1068,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG 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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1068,6 +1100,9 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af h1:aLMMXFYqw01RA6XJim5uaN+afqNNjc9P8HPAbnpnc5s= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 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= @@ -1092,8 +1127,10 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= 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= diff --git a/pkg/client/client.go b/pkg/client/client.go index a0dfddacb..e690f8ada 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -61,7 +61,7 @@ func (c *client) GetFolder(ctx context.Context, id string, into *types.Folder) e } func (c *client) ListFolders(ctx context.Context, into *types.FolderList) error { - return c.c.Get().Path(fmt.Sprintf("/apis/core.nrc.no/v1/folders")).Do(ctx).Into(into) + return c.c.Get().Path("/apis/core.nrc.no/v1/folders").Do(ctx).Into(into) } func (c *client) DeleteFolder(ctx context.Context, id string) error { diff --git a/pkg/server/formsapi/handlers/database/create.go b/pkg/server/formsapi/handlers/database/create.go index c8c75ae24..2ab3eb6ab 100644 --- a/pkg/server/formsapi/handlers/database/create.go +++ b/pkg/server/formsapi/handlers/database/create.go @@ -9,6 +9,7 @@ import ( "github.com/nrc-no/core/pkg/utils" uuid "github.com/satori/go.uuid" "go.uber.org/zap" + "log" "net/http" ) @@ -43,6 +44,12 @@ func (h *Handler) Create() http.HandlerFunc { return } + resp, err := h.zanzibarClient.WriteDB2UserRel(ctx, db.ID, "userId") + if err != nil { + log.Fatalf("failed to create relationship between database and creator: %s, %s", err, resp) + return + } + utils.JSONResponse(w, http.StatusOK, respDB) } } diff --git a/pkg/server/formsapi/handlers/database/handler.go b/pkg/server/formsapi/handlers/database/handler.go index eed0d43de..59568f8cc 100644 --- a/pkg/server/formsapi/handlers/database/handler.go +++ b/pkg/server/formsapi/handlers/database/handler.go @@ -7,17 +7,21 @@ import ( "github.com/nrc-no/core/pkg/api/types" "github.com/nrc-no/core/pkg/constants" "github.com/nrc-no/core/pkg/store" + "github.com/nrc-no/core/pkg/zanzibar" "net/http" ) type Handler struct { - store store.DatabaseStore - webService *restful.WebService + store store.DatabaseStore + webService *restful.WebService + zanzibarClient zanzibar.ZanzibarClient } -func NewHandler(store store.DatabaseStore) *Handler { +func NewHandler(store store.DatabaseStore, z zanzibar.ZanzibarClient) *Handler { h := &Handler{store: store} + h.zanzibarClient = z + ws := new(restful.WebService). Path("/apis/core.nrc.no/v1/databases"). Doc("databases.core.nrc.no API") diff --git a/pkg/server/formsapi/server.go b/pkg/server/formsapi/server.go index 6dec119e0..228af18b9 100644 --- a/pkg/server/formsapi/server.go +++ b/pkg/server/formsapi/server.go @@ -9,6 +9,7 @@ import ( "github.com/nrc-no/core/pkg/server/generic" "github.com/nrc-no/core/pkg/server/options" "github.com/nrc-no/core/pkg/store" + client2 "github.com/nrc-no/core/pkg/zanzibar" ) type Server struct { @@ -19,6 +20,7 @@ type Server struct { type Options struct { options.ServerOptions StoreFactory store.Factory + ZanzibarClient client2.ZanzibarClient } func NewServer(options Options) (*Server, error) { @@ -31,7 +33,7 @@ func NewServer(options Options) (*Server, error) { container := genericServer.GoRestfulContainer databaseStore := store.NewDatabaseStore(options.StoreFactory) - databaseHandler := database.NewHandler(databaseStore) + databaseHandler := database.NewHandler(databaseStore, options.ZanzibarClient) container.Add(databaseHandler.WebService()) folderStore := store.NewFolderStore(options.StoreFactory) diff --git a/pkg/server/options/options.go b/pkg/server/options/options.go index 13007a75e..ad19a0004 100644 --- a/pkg/server/options/options.go +++ b/pkg/server/options/options.go @@ -100,8 +100,14 @@ type HydraOptions struct { } type Options struct { - Serve ServeOptions `mapstructure:"serve"` - DSN string `mapstructure:"dsn"` - Log LogOptions `mapstructure:"log"` - Hydra HydraOptions `mapstructure:"hydra"` + Serve ServeOptions `mapstructure:"serve"` + DSN string `mapstructure:"dsn"` + Log LogOptions `mapstructure:"log"` + Hydra HydraOptions `mapstructure:"hydra"` + Zanzibar ZanzibarOptions `mapstructure:"zanzibar"` +} + +type ZanzibarOptions struct { + Token string `mapstructure:"token"` + Prefix string `mapstructure:"prefix"` } diff --git a/pkg/zanzibar.go b/pkg/zanzibar.go new file mode 100644 index 000000000..242044717 --- /dev/null +++ b/pkg/zanzibar.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "log" + + pb "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/authzed-go/v1" + "github.com/authzed/grpcutil" +) + +const schema = `definition nrc_test/user {} + +definition nrc_test/post { + relation reader: nrc_test/user + relation writer: nrc_test/user + + permission read = reader + writer + permission write = writer +}` + +func main() { + client, err := authzed.NewClient( + "grpc.authzed.com:443", + grpcutil.WithBearerToken("tc_nrc_test_default_token"), + grpcutil.WithSystemCerts(grpcutil.VerifyCA), + ) + if err != nil { + log.Fatalf("unable to initialize client: %s", err) + } + + request := &pb.WriteSchemaRequest{Schema: schema} + resp, err := client.WriteSchema(context.Background(), request) + if err != nil { + log.Fatalf("failed to write schema: %s", resp) + } +} diff --git a/pkg/zanzibar/zanzibar_client.go b/pkg/zanzibar/zanzibar_client.go new file mode 100644 index 000000000..e593e09ac --- /dev/null +++ b/pkg/zanzibar/zanzibar_client.go @@ -0,0 +1,70 @@ +package zanzibar + +import ( + "context" + pb "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/authzed-go/v1" + "github.com/authzed/grpcutil" + "log" +) + +type ZanzibarClient interface { + WriteDB2UserRel(ctx context.Context, databaseId string, userId string) (*pb.WriteRelationshipsResponse, error) +} + +type ZanzibarClientConfig struct { + Token string + Prefix string +} + +func NewZanzibarClient(c ZanzibarClientConfig) *zanzibarClient { + client, err := authzed.NewClient( + "grpc.authzed.com:443", + grpcutil.WithBearerToken(c.Token), // TODO get token from config + grpcutil.WithSystemCerts(grpcutil.VerifyCA), + ) + if err != nil { + log.Fatalf("unable to initialize client: %s", err) + } + + return &zanzibarClient{ + z: client, + prefix: c.Prefix, + } +} + +type zanzibarClient struct { + z *authzed.Client + prefix string +} + +func (c *zanzibarClient) WriteDB2UserRel(ctx context.Context, databaseId string, userId string) (*pb.WriteRelationshipsResponse, error) { + r := &pb.WriteRelationshipsRequest{ + Updates: []*pb.RelationshipUpdate{ + { + Relationship: &pb.Relationship{ + Relation: "creator", + Resource: &pb.ObjectReference{ + ObjectType: c.prefix + "/database", // TODO get prefix from config + ObjectId: databaseId, + }, + Subject: &pb.SubjectReference{ + Object: &pb.ObjectReference{ + ObjectType: c.prefix + "/user", // TODO get prefix from config + ObjectId: userId, + }, + }, + }, + Operation: pb.RelationshipUpdate_OPERATION_CREATE, + }, + }, + } + + resp, err := c.z.WriteRelationships(ctx, r) + + if err != nil { + log.Fatalf("failed to create relationship between database and creator: %s, %s", err, resp) + return nil, err + } + return resp, nil +}