A sample web application to show how you can integrate Oso.
Here are some demo Gifs to see how the app looks like:
The stack:
- Server
- Node.js(TypeScript) + Express + TypeOrm
- Client
- React
- A request is authorized if it includes a
x-user-id
HTTP Header- Otherwise,
401
will be returned
- Otherwise,
- If the logged-in user is
- a member of the HR department
- the user
- can view all the members of the organization
- can edit all the members of the organization
- the user
- not a member of the HR department
- the user
- can view members which belong to the same department
- the user
- an admin user
- the user
- can use the admin features of the service (can be accessed from the user menu)
- the user
- a member of the HR department
- If the logged-in user is
- a member of the HR department
- the user
- can view all the members' fields including private fields (such as salary)
- can edit all the updatable fields including private fields (such as salary)
- the user
- not a member of the HR department
- the user
- can view other members' public fields only
- can view private fields of the logged in user
- can edit public fields of the logged in user (not private fields such as salary)
- the user
- a member of the HR department
Basically:
- The UseCaseController receives a request
- The UseCaseController invokes the UseCaseService's method
- The UseCaseService invokes the UseCaseRepository to fetch data
- The UseCaseRepository fetches data from SQLite (Calls the DataFilter if filtering is necessary)
- The UseCaseService enforces field-level authorization and returns results to the UseCaseController
- The UseCaseService invokes the UseCaseRepository to fetch data
- The UseCaseController sends the response to the requester
This example creates 2 Oso instances:
- 1 for the ORM models
- 1 for the Core models
This is required because the ORM models' field names differ from the Core models. For example, the MemberOrm model has a departmentId
fields but the Member model does not. Hence, the Polar differs:
The ORM Polar:
has_permission(user: User, "read", member: Member) if
user.member.department.id = member.departmentId or
user.member.department.name = "hr";
The Core Polar:
has_permission(user: User, "read", member: Member) if
user.memberInfo.department.id = member.department.id or
user.memberInfo.department.name = "hr";
Look how the field names user.member
and user.memberInfo
differ. You can rename the Core models to use user.member
, but, imo field names shouldn't depend on tools and frameworks. They should be isolated.
authorizedQuery
You can see the authorizedQuery being used in repository implementations. By using the authorizedQuery
you can add more query options such as sort.
authorizedFields
authorizedFields can be found in UseCaseServices. It is used to authorize request bodies when there is a mutation request such as PATCHES.
authorizedActions
authorizedActions can be found in UseCaseServices. It is used to check if the requester has permission to do an action to a specific resource (e.g. can the user update the resource?)
cd server
# install dependencies
npm install
# seed the SQLite database
npm run seed:refresh
# start the server
npm run dev
cd client
# install dependencies
npm install
# start the front-end locally
npm start
Access http://localhost:3000/login and you'll see a page like the one below:
Select a user and you will be logged in as the selected user for further requests.