|
| 1 | +# HR sample web application using Oso as AuthZ |
| 2 | + |
| 3 | +A sample web application to show how you can integrate Oso. |
| 4 | + |
| 5 | +Here are some demo Gifs to see how the app looks like: |
| 6 | +- [Login with HR user](assets/oso_demo_hr.gif) |
| 7 | +- [Login with Admin non-HR user](assets/oso_demo_admin.gif) |
| 8 | +- [Login with non-Admin non-HR user](assets/oso_demo_non_admin.gif) |
| 9 | + |
| 10 | +The stack: |
| 11 | + |
| 12 | +- Server |
| 13 | + - Node.js(TypeScript) + Express + TypeOrm |
| 14 | +- Client |
| 15 | + - React |
| 16 | + |
| 17 | +## ER |
| 18 | + |
| 19 | + |
| 20 | +## Types of authorization |
| 21 | + |
| 22 | +### Request-level authorization |
| 23 | + |
| 24 | +- A request is authorized if it includes a `x-user-id` HTTP Header |
| 25 | + - Otherwise, `401` will be returned |
| 26 | + |
| 27 | +### Resource-level authorization |
| 28 | + |
| 29 | +- If the logged-in user is |
| 30 | + - a member of the HR department |
| 31 | + - the user |
| 32 | + - can view all the members of the organization |
| 33 | + - can edit all the members of the organization |
| 34 | + - not a member of the HR department |
| 35 | + - the user |
| 36 | + - can view members which belong to the same department |
| 37 | + - an admin user |
| 38 | + - the user |
| 39 | + - can use the admin features of the service (can be accessed from the user menu) |
| 40 | + |
| 41 | +### Field-level authorization |
| 42 | + |
| 43 | +- If the logged-in user is |
| 44 | + - a member of the HR department |
| 45 | + - the user |
| 46 | + - can view all the members' fields including private fields (such as salary) |
| 47 | + - can edit all the updatable fields including private fields (such as salary) |
| 48 | + - not a member of the HR department |
| 49 | + - the user |
| 50 | + - can view other members' public fields only |
| 51 | + - can view private fields of the logged in user |
| 52 | + - can edit public fields of the logged in user (not private fields such as salary) |
| 53 | + |
| 54 | +## Architecture Overview |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +Basically: |
| 59 | +1. The UseCaseController receives a request |
| 60 | +2. The UseCaseController invokes the UseCaseService's method |
| 61 | + 1. The UseCaseService invokes the UseCaseRepository to fetch data |
| 62 | + 1. The UseCaseRepository fetches data from SQLite (Calls the DataFilter if filtering is necessary) |
| 63 | + 2. The UseCaseService enforces field-level authorization and returns results to the UseCaseController |
| 64 | +3. The UseCaseController sends the response to the requester |
| 65 | + |
| 66 | +### Why use 2 Oso instances? |
| 67 | + |
| 68 | +This example creates 2 Oso instances: |
| 69 | + |
| 70 | +* 1 for the ORM models |
| 71 | +* 1 for the Core models |
| 72 | + |
| 73 | +This is required because the ORM models' field names differ from the Core models. For example, the [MemberOrm](./server/src/members/shared/typeorm/memberOrm.ts) model has a `departmentId` fields but the [Member](./server/src/members/shared/member.ts) model does not. Hence, the Polar differs: |
| 74 | + |
| 75 | +The ORM Polar: |
| 76 | + |
| 77 | +```sh |
| 78 | +has_permission(user: User, "read", member: Member) if |
| 79 | + user.member.department.id = member.departmentId or |
| 80 | + user.member.department.name = "hr"; |
| 81 | +``` |
| 82 | +
|
| 83 | +The Core Polar: |
| 84 | +
|
| 85 | +```sh |
| 86 | +has_permission(user: User, "read", member: Member) if |
| 87 | + user.memberInfo.department.id = member.department.id or |
| 88 | + user.memberInfo.department.name = "hr"; |
| 89 | +``` |
| 90 | +
|
| 91 | +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. |
| 92 | +
|
| 93 | +### Where Oso is used |
| 94 | +
|
| 95 | +#### Data filtering |
| 96 | +
|
| 97 | +**authorizedQuery** |
| 98 | +
|
| 99 | +You can see the [authorizedQuery](https://docs.osohq.com/node/guides/data_filtering.html) being used in [repository implementations](https://github.com/kenfdev/hr-sample-app/blob/422bc189b010d8896f3fef04b726392453b1d1ef/server/src/members/list-all-members/repository/listAllMembersSqliteRepository.ts#L21). By using the `authorizedQuery` you can add more query options such as sort. |
| 100 | +
|
| 101 | +**authorizedFields** |
| 102 | +
|
| 103 | +[authorizedFields](https://docs.osohq.com/node/reference/api/classes/oso.oso-1.html#authorizedfields) can be found in UseCaseServices. It is used to [authorize request bodies](https://github.com/kenfdev/hr-sample-app/blob/422bc189b010d8896f3fef04b726392453b1d1ef/server/src/members/edit-member-detail/editMemberDetailService.ts#L30-L34) when there is a mutation request such as PATCHES. |
| 104 | +
|
| 105 | +**authorizedActions** |
| 106 | +
|
| 107 | +[authorizedActions](https://docs.osohq.com/node/reference/api/classes/oso.oso-1.html#authorizedactions) can be found in UseCaseServices. It is used to [check if the requester has permission to do an action to a specific resource](https://github.com/kenfdev/hr-sample-app/blob/422bc189b010d8896f3fef04b726392453b1d1ef/server/src/members/show-member-detail/showMemberDetailService.ts#L40-L50) (e.g. can the user update the resource?) |
| 108 | +
|
| 109 | +## How to start |
| 110 | +
|
| 111 | +### Server |
| 112 | +
|
| 113 | +```sh |
| 114 | +cd server |
| 115 | +
|
| 116 | +# install dependencies |
| 117 | +npm install |
| 118 | +
|
| 119 | +# seed the SQLite database |
| 120 | +npm run seed:refresh |
| 121 | +
|
| 122 | +# start the server |
| 123 | +npm run dev |
| 124 | +``` |
| 125 | +
|
| 126 | +### Client |
| 127 | +
|
| 128 | +```sh |
| 129 | +cd client |
| 130 | +
|
| 131 | +# install dependencies |
| 132 | +npm install |
| 133 | +
|
| 134 | +# start the front-end locally |
| 135 | +npm start |
| 136 | +``` |
| 137 | +
|
| 138 | +### Start playing around |
| 139 | +
|
| 140 | +Access http://localhost:3000/login and you'll see a page like the one below: |
| 141 | + |
| 142 | +
|
| 143 | +Select a user and you will be logged in as the selected user for further requests. |
0 commit comments