Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check user authorization in realm api requests #975

Merged

Conversation

jurgenwerk
Copy link
Contributor

@jurgenwerk jurgenwerk commented Jan 17, 2024

This PR adds authentication and authorization to all our realm's endpoints. If the http method is PUT, PATCH, POST, DELETE, then the request is considered a ✍️ write request, otherwise it's a 📖 read request.

There's a new checkPermission function that will:

  1. Authenticate the user using the provided JWT
  • If realm is publicly readable or writable it will not require a JWT
  • Otherwise it will require a JWT. Will respond with 401 if JWT is missing or invalid.
  1. Check whether the current user has a permission to read when reading, and write when writing
  • It will respond with 403 if there is no permission to perform the action
  • Otherwise it will proceed to process the request

Notes

  1. The realm tests have been refactored to support testing in realms with different permissions
  2. The realm permissions config is now passed to the constructor of the realm (as opposed to the previous way of lazily reading it from the environment variable when permissions are checked). This has been changed so that it is more encapsulated and thus easier to test (can easily create different realms with different permissions in the same environment)
  3. All the occurences of REALM_USER_PERMISSIONS="{}" in our realm start scripts have been removed. .realms.json.test and realms.json.development files have been introduced (permissions config) for better experience when changing permissions during development or for testing. However, in our deployed environments (not development or test), REALM_USER_PERMISSIONS is a mandatory ENV variable. This is because in deployed environments it's easier to manage an ENV variable compared to file copying approach

Future work

Currently, all our deployed realms are public readable and writable. They will need to stay this way until we add support for realms to be be able to authenticate between each other, otherwise indexing cards from other realms (e.g. linked cards) will fail if there is no permission to read.

Copy link

github-actions bot commented Jan 17, 2024

Test Results

491 tests  ±0   487 ✔️ ±0   6m 48s ⏱️ +13s
    1 suites ±0       4 💤 ±0 
    1 files   ±0       0 ±0 

Results for commit bec1cf1. ± Comparison against base commit 24ead8b.

♻️ This comment has been updated with latest results.

@jurgenwerk jurgenwerk marked this pull request as ready for review January 29, 2024 11:05
@jurgenwerk jurgenwerk requested a review from a team January 29, 2024 11:07
Copy link
Contributor

@lukemelia lukemelia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a few suggestions. Otherwise good from my POV

@@ -9,7 +9,8 @@ import { makeFastBootIndexRunner } from './fastboot';
import { RunnerOptionsManager } from '@cardstack/runtime-common/search-index';
import { readFileSync } from 'fs-extra';
import { shimExternals } from './lib/externals';
import RealmPermissions from './lib/realm-permissions';
import { RealmPermissions as RealmPermissionsInterface } from '@cardstack/runtime-common/realm';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { RealmPermissions as RealmPermissionsInterface } from '@cardstack/runtime-common/realm';
import { type RealmPermissions as RealmPermissionsInterface } from '@cardstack/runtime-common/realm';

@@ -157,7 +156,51 @@ let realmPermissions = new RealmPermissions();
dist,
manager.getOptions.bind(manager),
);
let permissions = realmPermissions.permissionsForRealm(url);

let userPermissions = {} as {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider extracting a method/util to keep realm-server/main tidy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I added this

let userPermissions = [
...(this.realmPermissions['*'] || []),
...(this.realmPermissions[username] || []),
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elegant

Comment on lines 755 to 761
if (e instanceof AuthorizationError) {
return new Response(`Unauthorized: ${e.message}`, { status: 401 });
}

if (e instanceof PermissionError) {
return new Response(`Not allowed: ${e.message}`, { status: 403 });
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the language typically used for this is Authentication (who are you?) and Authorization (what are you allowed to do?). Maybe those would be better names than Authorization and Permission?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I initially used AuthorizationError for authentication errors because of the Authorization header but Authenticaton and Authorization do describe this better. I adjusted this

@jurgenwerk jurgenwerk requested a review from a team January 29, 2024 18:46
@jurgenwerk
Copy link
Contributor Author

I've addressed all the review comments.

Also, this PR is somewhat sensitive to the deployment due to ENV var requirements and keeping things work for the world readable realms so I test deployed this branch to our staging. There was a problem that the new error from this PR handling caught successfully - the permissions config (parameter store) was faulty (realm keys were names when they should be URLs). I fixed that issue and redeployed to staging and things seem to be working ok. I've also addressed this issue in production param store.

@jurgenwerk jurgenwerk merged commit d75de62 into main Jan 30, 2024
20 checks passed
@delete-merged-branch delete-merged-branch bot deleted the cs-6438-check-authorization-of-user-on-each-api-request branch January 30, 2024 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants