Starchart: Matching Database With SAML Claims
Tags: webdev, opensource, prisma, javascript
Published: 2023-03-11 03:04:38

## Task
[What I did](https://github.com/DevelopingSpace/starchart/pull/307) this week was matching `User` table of database in [Starchart](https://github.com/DevelopingSpace/starchart) project with what useful information [SAML](https://en.wikipedia.org/wiki/SAML_2.0) would return about users when users log in. 
This meant adding `group` column in `User` table in database, given it's one of the claims. `group` claim is used as role assignment, such as user being part of student group, which has specific access attached to the group.
This project uses [Prisma](https://www.prisma.io/) to interact with the database.

There were 3 things I needed to do:
1. Edit the schema file (`prisma\schema.prisma`).
2. Edit the file (`prisma\seed.ts`) for seeding to make sure these seeded users have groups. 
3. Chang how relevant functions interact with the database, so a function that make Prisma API call to create a user would need to be changed for the addition of `group`. When changing a function definition, one needs to remember to change how the function is used in other area of your code. For example, I changed the parameter of `createUser()` to allow argument for `group`, so I must change how the function is used elsewhere in the project too.
4. Cover new use cases for the column. This meant I added 3 functions to check whether a user belongs to one of 3 groups. The functions `isStudent()`, `isFaculty()`, and `isAdmin()` accept `username` as parameter. They would return `true` if they have the role/group, `false` if not. The code look something like this:

     ```
     export async function isFaculty(username: 
     PrismaUser['username']) {
       const { group } = await prisma.user.findUniqueOrThrow({ where: { username } });
       // The group will have -dev in it on staging but not on prod
       return /mycustomdomain(-dev)?-faculty/.test(group);
     }
     ```
     `async` marks this function as asynchronous, since it needs to interact with the database and `await` a response.

     The `group` value will be returned by the Prisma function after it fetches the user, by using the primary key `username`. Notice typescript syntax is used to limit the type of `username` according to Prisma `User` model's `username` column type.]
     `findUniqueOrThrow()` will throw error if nothing is found, as the function name suggests. You can see the documentation [here](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#finduniqueorthrow).

     After `group` is returned, a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) is used and [test](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) against the `group` value. If there's a match, it will return `true`, or `false` in the case of no match. This looks for any `mycustomdomain-faculty` or `mycustomdomain-dev-faculty` in `group`.

5. Consider other adjustment to codes relating to `User`. There was no need for additional adjustment for this, but I could easily needed to do so. For example, if I changed a column name, I would need to change other references to the name in the project.

Also, I merged `firstName` and `lastName` columns into one `displayName` column, given that's what SAML gives back. The same ideas for such changes in database schema apply: edit the schema file, edit the seeding file, change/add relevant functions, and other relevant code adjustment. In this case, no new function was added.


## Change Log Of The PR:
* Add group in `User` model as String in `prisma\schema.prisma`
* Add examples in `prisma\seed.ts`
* Modify `createUser()` in `app\models\user.server.ts` accordingly
* Add `isStudent()`, `isFaculty()`, and `isAdmin()` in `app\models\user.server.ts`.
* Combine `firstName` and `lastName` into `displayName`
* Modify arguments of `createUser()` in `app\routes\login\callback.tsx`

`isStudent()`, `isFaculty()`, and `isAdmin()` accept `username` as parameter. They would return `true` if they have the role/group, `false` if not.