Skip to content

Commit

Permalink
add some decision documents
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Aug 14, 2023
1 parent fe1494f commit e979471
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 0 deletions.
37 changes: 37 additions & 0 deletions docs/decisions/026-path-aliases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Path Aliases

Date: 2023-08-14

Status: accepted

## Context

It's pretty common to configure TypeScript to have path aliases for imports.
This allows you to avoid relative imports and makes it easier to move files
around without having to update imports.

When the Epic Stack started, we used path imports that were similar to those in
the rest of the Remix ecosystem: `~/` referenced the `app/` directory. We added
`tests/` to make it easier to import test utils.

However, we've found that this is confusing for new developers. It's not clear
what `~/` means, and seeing `import { thing } from 'tests/thing'` is confusing.
I floated the idea of adding another alias for `@/` to be the app directory and
or possibly just moving the `~/` to the root and having that be the only alias.
But at the end of the day, we're using TypeScript which will prevent us from
making mistakes and modern editors will automatically handle imports for you
anyway.

At first it may feel like a pain, but less tooling magic is better and editors
can really help reduce the pain. Additionally, we have ESLint configured to sort
imports for us so we don't have to worry about that either. Just let the editor
update the imports and let ESLint sort them.

## Decision

Remove the path aliases from the `tsconfig`.

## Consequences

This requires updating all the imports that utilized the path aliases to use
relative imports.
36 changes: 36 additions & 0 deletions docs/decisions/027-toasts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Toasts

Date: 2023-08-14

Status: accepted

## Context

In the Epic Stack we used the Shadcn toast implementation. This worked ok, but
it did require a lot of custom code for ourselves and did a poor job of managing
multiple toast messages.

We also had a shared `flash` session implementation for both toasts and
confetti. This was overly complex.

There's another library
[someone told me about](https://twitter.com/ayushverma1194/status/1674848096155467788)
that is a better fit. It's simpler and has an API sufficient to our use cases.

It's also sufficiently customizable from a design perspective as well. And it's
actively developed.

## Decision

Remove our own toast implementation and use the library instead.

Also separate the toast and confetti session implementations. Toasts can
continue to use a regular session, but confetti will be a much simpler cookie.

## Consequences

This will limit the level of customizability because we're now relying on a
library for managing toast messages, however it also reduces the maintenance
burden for users of the Epic Stack.

This will also simplify the confetti implementation.
84 changes: 84 additions & 0 deletions docs/decisions/028-permissions-rbac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Permissions (RBAC)

Date: 2023-08-14

Status: accepted

## Context

Originally, the Epic Stack had a `role` and `permission` model which was quite
limited in its use case. It was not very useful and not based on any real world
scenario:

```prisma
model Role {
id String @id @unique @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
permissions Permission[]
}
model Permission {
id String @id @unique @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
roles Role[]
}
```

There are various ways to implement permissions, but a common approach is called
[Role Based Access Control (RBAC)](https://auth0.com/intro-to-iam/what-is-role-based-access-control-rbac).
This is a very flexible approach and can be used in many different ways. As a
more established approach it's also easier to find resources to learn about and
understand it.

## Decision

We're changing the implementation to follow a RBAC model:

```prisma
model Permission {
id String @id @default(cuid())
action String // e.g. create, read, update, delete
entity String // e.g. note, user, etc.
access String // e.g. own or any
description String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
roles Role[]
@@unique([action, entity, access])
}
model Role {
id String @id @default(cuid())
name String @unique
description String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
permissions Permission[]
}
```

This allows us to have much more fine grained control over our permissions.
Additionally, we can create utilities for determining whether a user has
permission to perform an action and disallow them from doing so if they do not.

## Consequences

This is a breaking change for the Epic Stack. Anyone wanting to adopt this
permissions model will need to perform a database migration. However, it's
important that we make this change now because the previous model was not great.
This one is.
22 changes: 22 additions & 0 deletions docs/decisions/029-remix-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Remix Auth

Date: 2023-08-14

Status: accepted

## Context

At the start of Epic Stack, we were using
[remix-auth-form](https://github.com/sergiodxa/remix-auth-form) for our
username/password auth solution. This worked fine, but it really didn't give us
any value over handling the auth song-and-dance ourselves.

## Decision

Instead of relying on remix-auth for handling authenticating the user's login
form submission, we'll manage it ourselves.

## Consequences

This mostly allows us to remove some code. However, we're going to be keeping
remix auth around for GitHub Auth
69 changes: 69 additions & 0 deletions docs/decisions/030-github-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# GitHub Auth

Date: 2023-08-14

Status: accepted

## Context

Many applications require integrating with third party authentication providers.
For this reason, we want to support the idea of "connections" as a built-in part
of the Epic Stack.

There are many different providers we could support, but many people need to
support more than just one. By building things in a way that allows us to
support more than just a single auth provider, it allows us to also make it easy
to swap to a different provider as needed.

Many auth providers support OAuth2, but increasingly, many are also supporting
OpenID Connect. OpenID Connect is a layer on top of OAuth2 that provides a
standardized way to get user information from the auth provider.

Sadly, GitHub (a common popular auth provider for many developer-focused apps)
does not support OpenID, however, by using
[`remix-auth`](https://github.com/sergiodxa/remix-auth), we can easily support
GitHub as a built-in implementation and allow people to swap it out for whatever
OAuth2 or OIDC auth provider they have (if OIDC, they can use
[web-oidc](https://github.com/sergiodxa/web-oidc)).

## Decision

We will update the database schema to support multiple auth providers with a
model called `Connection`:

```prisma
model Connection {
id String @id @default(cuid())
providerName String
providerId String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
@@unique([providerName, providerId])
@@unique([providerId, userId])
}
```

We'll also build the appropriate callback URL handlers and UI to allow people to
manage their connections.

## Consequences

With third party auth, this means that users may not have passwords. So we'll
need to handle that situation and allow users to onboard without the use of
passwords. We'll also need to prevent them from deleting all their connections
until they've created a password.

There are a number of states for the user to be in within the callback as well
which all will need to be considered. All of these states will be tested to
ensure they continue to function properly as people tune things for their needs.

Additionally, we'll need to account for the fact that some folks don't want to
set up the GitHub login flow from the start (to keep in line with our
[Minimize Setup Friction guiding principle](../guiding-principles.md)), so we'll
have to make sure that the app still runs properly without GitHub auth
configured.
42 changes: 42 additions & 0 deletions docs/permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Permissions

The Epic Stack's Permissions model takes after
[Role-Based Access Control (RBAC)](https://auth0.com/intro-to-iam/what-is-role-based-access-control-rbac).
Each user has a set of roles, and each role has a set of permissions. A user's
permissions are the union of the permissions of all their roles (with the more
permissive permission taking precedence).

The default development seed creates fine-grained permissions that include
`create`, `read`, `update`, and `delete` permissions for `user` and `note` with
the access of `own` and `any`. The default seed also creates `user` and `admin`
roles with the sensible permissions for those roles.

You can combine these permissions in different ways to support different roles
for different personas of users of your application.

The Epic Stack comes with built-in utilities for working with these permissions.
Here are some examples to give you an idea:

```ts
// server-side only utilities
const userCanDeleteAnyUser = await requireUserWithPermission(
request,
'delete:user:any',
)
const userIsAdmin = await requireUserWithRole(request, 'admin')
```

```ts
// UI utilities
const user = useUser()
const userCanCreateTheirOwnNotes = userHasPermission(user, 'create:note:own')
const userIsUser = userHasRole(user, 'user')
```

There is currently no UI for managing permissions, but you can use prisma studio
for establishing these.

## Seeding the production database

Check [the deployment docs](./deployment.md) for instructions on how to seed the
production database with the roles you want.

0 comments on commit e979471

Please sign in to comment.