/hr-sample-app

Sample web application integrating with Oso

Primary LanguageTypeScript

CI

HR sample web application using Oso as AuthZ

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

ER

Image

Types of authorization

Request-level authorization

  • A request is authorized if it includes a x-user-id HTTP Header
    • Otherwise, 401 will be returned

Resource-level authorization

  • 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
    • not a member of the HR department
      • the user
        • can view members which belong to the same department
    • an admin user
      • the user
        • can use the admin features of the service (can be accessed from the user menu)

Field-level authorization

  • 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)
    • 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)

Architecture Overview

Image

Basically:

  1. The UseCaseController receives a request
  2. The UseCaseController invokes the UseCaseService's method
    1. The UseCaseService invokes the UseCaseRepository to fetch data
      1. The UseCaseRepository fetches data from SQLite (Calls the DataFilter if filtering is necessary)
    2. The UseCaseService enforces field-level authorization and returns results to the UseCaseController
  3. The UseCaseController sends the response to the requester

Why use 2 Oso instances?

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.

Where Oso is used

Data filtering

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?)

How to start

Server

cd server

# install dependencies
npm install

# seed the SQLite database
npm run seed:refresh

# start the server
npm run dev

Client

cd client

# install dependencies
npm install

# start the front-end locally
npm start

Start playing around

Access http://localhost:3000/login and you'll see a page like the one below: Image

Select a user and you will be logged in as the selected user for further requests.