Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/releaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: write
jobs:
ldflags_args:
runs-on: ubuntu-latest
runs-on: self-hosted
outputs:
commit-date: ${{ steps.ldflags.outputs.commit-date }}
commit: ${{ steps.ldflags.outputs.commit }}
Expand All @@ -35,7 +35,7 @@ jobs:
permissions:
contents: write # To add assets to a release.
id-token: write # To do keyless signing with cosign
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion config/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ CORE_AUTH_TOKEN_APITOKENS_PREFIX=""
CORE_AUTH_TOKEN_ASSESSMENTACCESSDURATION="1h"
CORE_AUTH_TOKEN_TRUSTCENTERNDAREQUESTACCESSDURATION="1h"
CORE_AUTH_SUPPORTEDPROVIDERS=""
CORE_AUTH_PROVIDERS_REDIRECTURL="http://localhost:3001/api/auth/callback/theopenlane"
CORE_AUTH_PROVIDERS_REDIRECTURL="http://localhost:3001/login/sso"
CORE_AUTH_PROVIDERS_GITHUB_CLIENTID=""
CORE_AUTH_PROVIDERS_GITHUB_CLIENTSECRET=""
CORE_AUTH_PROVIDERS_GITHUB_CLIENTENDPOINT=""
Expand Down
2 changes: 1 addition & 1 deletion config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ auth:
clientsecret: ""
redirecturl: /v1/google/callback
scopes: []
redirecturl: http://localhost:3001/api/auth/callback/theopenlane
redirecturl: http://localhost:3001/login/sso
webauthn:
debug: false
displayname: ""
Expand Down
2 changes: 1 addition & 1 deletion config/helm-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ coreConfiguration:
# -- Providers contains supported oauth2 providers configuration
providers:
# -- RedirectURL is the URL that the OAuth2 client will redirect to after authentication is complete
redirecturl: "http://localhost:3001/api/auth/callback/theopenlane" # @schema type:string; default:http://localhost:3001/api/auth/callback/theopenlane
redirecturl: "http://localhost:3001/login/sso" # @schema type:string; default:http://localhost:3001/login/sso
# -- Github contains the configuration settings for the Github Oauth Provider
github:
clientid: "" # @schema type:string
Expand Down
80 changes: 59 additions & 21 deletions db/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,28 @@ All tuples where:
- `_user LIKE 'organization:%'`
- `object_type != 'file'` (files use a separate ownership model)

### Steps
---

**1. Preview the rows that will be migrated**
## Pre-release steps

**1. Preview what will be migrated**

```sql
SELECT
store,
object_type,
object_id,
'parent_context' AS relation,
_user,
user_type
SELECT object_type, COUNT(*) AS row_count
FROM tuple
WHERE relation = 'parent'
AND _user LIKE 'organization:%'
AND object_type != 'file';
AND object_type != 'file'
GROUP BY object_type
ORDER BY object_type;
```

**2. Run the migration**
**2. Record the migration start time, then run the migration**

```sql
-- record this value; you will need it for rollback if something goes wrong
SELECT NOW() AS migration_start;

INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
SELECT
store,
Expand All @@ -46,7 +47,7 @@ SELECT
'parent_context',
_user,
user_type,
md5(store || object_type || object_id || 'parent_context' || _user),
generate_ulid(),
NOW()
FROM tuple
WHERE relation = 'parent'
Expand All @@ -55,32 +56,69 @@ WHERE relation = 'parent'
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING;
```

The `ulid` is derived deterministically from the natural key so the insert is idempotent — safe to re-run.
**3. Verify row counts match step 1**

```sql
SELECT object_type, COUNT(*) AS migrated
FROM tuple
WHERE relation = 'parent_context'
AND _user LIKE 'organization:%'
GROUP BY object_type
ORDER BY object_type;
```

**If something looks wrong — rollback before releasing**

**3. Verify**
Substitute `$migration_start` with the timestamp recorded in step 2.

Spot-check that `parent_context` rows now exist for the same objects that had `parent` rows:
```sql
DELETE FROM tuple
WHERE relation = 'parent_context'
AND _user LIKE 'organization:%'
AND inserted_at >= '$migration_start';
```

---

## Release

Deploy the updated FGA model after the tuples are written. Deploying the model before the migration means objects will temporarily lose org-context permissions.

---

## Post-release steps

**4. Verify the new model is using `parent_context`**

Spot-check a known object in a known org and confirm permissions resolve correctly. Then confirm the counts are still what you expect:

```sql
SELECT object_type, COUNT(*)
SELECT object_type, COUNT(*) AS migrated
FROM tuple
WHERE relation = 'parent_context'
AND _user LIKE 'organization:%'
GROUP BY object_type
ORDER BY object_type;
```

**4. Deploy the updated FGA model**
**5. Clean up old `parent` tuples**

The new model must be deployed after the tuples are written. Deploying the model before the migration means objects will temporarily lose org-context permissions.
Once the new model is live and verified, the old `parent` + `organization:*` tuples are no longer read by the model and can be deleted:

**5. (Optional) Clean up old `parent` tuples**
```sql
DELETE FROM tuple
WHERE relation = 'parent'
AND _user LIKE 'organization:%'
AND object_type != 'file';
```

Once the new model is live and verified, the old `parent` + `organization:*` tuples are no longer used and can be deleted:
Confirm the expected number of rows were removed:

```sql
DELETE FROM tuple
SELECT COUNT(*)
FROM tuple
WHERE relation = 'parent'
AND _user LIKE 'organization:%'
AND object_type != 'file';
-- should return 0
```
3 changes: 2 additions & 1 deletion docker/docker-compose-fga.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ services:
- OPENFGA_LOG_FORMAT=json
- OPENFGA_DATASTORE_MAX_OPEN_CONNS=40 #see postgres container
- OPENFGA_DATASTORE_MAX_IDLE_CONNS=40
- OPENFGA_PLAYGROUND_ENABLED=true
- OPENFGA_LIST_USERS_MAX_RESULTS=1000
- OPENFGA_LIST_OBJECTS_MAX_RESULTS=1000
- OPENFGA_MAX_CHECKS_PER_BATCH_CHECK=500
Expand All @@ -35,6 +34,8 @@ services:
- --datastore-metrics-enabled
- --metrics-enable-rpc-histograms
- --authn-method=none
- --playground-enabled
- --playground-addr=0.0.0.0:3000
healthcheck:
test: ["CMD", "/usr/local/bin/grpc_health_probe", "-addr=openfga:8081"]
interval: 5s
Expand Down
15 changes: 14 additions & 1 deletion internal/graphapi/contact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ func TestQueryContacts(t *testing.T) {
}

func TestMutationCreateContact(t *testing.T) {
// test scopes return error, this is also to test that write -> gives read
apiClientNoContactScope := setupAPIToken(sharedTestUser1.UserCtx, t, []string{"control:write"})
apiClientWithSpecificScope := setupAPIToken(sharedTestUser1.UserCtx, t, []string{"contact:write"})

testCases := []struct {
name string
request testclient.CreateContactInput
Expand Down Expand Up @@ -226,9 +230,18 @@ func TestMutationCreateContact(t *testing.T) {
request: testclient.CreateContactInput{
FullName: lo.ToPtr("Rhaenys Targaryen"),
},
client: suite.client.apiWithToken,
client: apiClientWithSpecificScope,
ctx: context.Background(),
},
{
name: "using api token without required scope",
request: testclient.CreateContactInput{
FullName: lo.ToPtr("Rhaenys Targaryen"),
},
client: apiClientNoContactScope,
ctx: context.Background(),
expectedErr: missingScopeErrorMsg,
},
{
name: "happy path, using pat",
request: testclient.CreateContactInput{
Expand Down
2 changes: 1 addition & 1 deletion internal/httpserve/handlers/oauth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
// OauthProviderConfig represents the configuration for OAuth providers such as Github and Google
type OauthProviderConfig struct {
// RedirectURL is the URL that the OAuth2 client will redirect to after authentication is complete
RedirectURL string `json:"redirecturl" koanf:"redirecturl" default:"http://localhost:3001/api/auth/callback/theopenlane"`
RedirectURL string `json:"redirecturl" koanf:"redirecturl" default:"http://localhost:3001/login/sso"`
// Github contains the configuration settings for the Github Oauth Provider
Github github.ProviderConfig `json:"github" koanf:"github"`
// Google contains the configuration settings for the Google Oauth Provider
Expand Down