管理者権限を付与する の履歴(No.2)

更新


プログラミング/svelte

権限を管理する

AuthUser と 多対多 関係を持つ Role を導入する。

prisma/schema.prisma

  model User {
  ...
+   roles         Role[]
  }
 
+ model Role {
+   id             String   @id @default(uuid())
+   name           String   @unique
+ 
+   users          User[]
+ }

マイグレーションする。

LANG: console
$ pnpm prisma migrate dev --name "add Role table"

Prisma で多対多関係を扱うためのコードは結構煩雑になるようなので、 Role を使うためのユーティリティ関数を db に持たせる。

このやり方が良いのかは疑問が残る?

src/lib/server/db.ts

LANG: ts
import { PrismaClient } from '@prisma/client';

export class ExtendedPrismaClient extends PrismaClient {
  constructor() { super() }

  // userId が undefined なら権限は空とみなされる
  async getRoles(userId: string | undefined) {
    if(!userId) {
      return []
    }
    return (await this.user.findUnique({
      where: {id: userId},
      select: {roles: true}
    }))?.roles || []
  }

  async getRolesString(userId: string | undefined) {
    return (await this.getRoles(userId)).map((role)=> role.name);
  }

  async hasRole(userId: string | undefined, role: string) {
    return (await this.getRolesString(userId)).includes(role);
  }

  async addRoles(userId: string, ...roles: string[]) {
    await this.user.update({
      where: { id: userId },
      data: {
        roles: {
          connectOrCreate: roles.map(role=>
            ({where: {name: role}, create: {name: role}})
          )
        },
      },
    })
  }

  async removeRoles(userId: string, ...roles: string[]) {
    await this.user.update({
      where: { id: userId },
      data: {
        roles: {
          deleteMany: roles.map(role=>({name: role}))
        },
      },
    })
  }
}

export const db = new ExtendedPrismaClient();

Prisma の言語仕様を見ていると TypeScript と VSCode の存在がいかに偉大か実感する。 こんな DSL はエディタ上でエラー検出してくれなきゃ、とてもじゃないけど書けない。

ところで、

LANG: ts
roles.map(role=>({name: role}))

などという記述に注意が必要。これを

LANG: ts
roles.map(role=>{name: role})

と書いてしまうと role=>{ の { がブロックの開始として解釈されてしまい、うまく行かない。 (その場合 name: はラベルとして解釈される)

JavaScriptのアロー関数でオブジェクトを返す方法
https://dev.classmethod.jp/articles/arrow-func-return-object/

そこで一見すると無駄に見える括弧で括っている。

サインアップ時、最初のユーザーには admin 権限を持たせることにする。

src/routes/account/(logout)/new/+page.server.ts

+ import { db } from '$lib/server/db'
...

+       // 最初のユーザーには admin 権限を持たせる
+       if(await db.user.count() == 1) {
+         await db.addRoles(user.userId, 'admin')
+       }

(admin) を含むパスには admin 権限を持っているユーザーしかアクセスできない。

src/hooks.server.ts

LANG:ts
+   if (event.route.id?.match(/\/\(admin\)\//) && !event.locals.session?.user) {
+     setFlash({ type: 'error', message: '管理者ユーザーのみアクセスできます' }, event);
+     throw redirect(302, path('/session/new'));
+   }
LANG: console
$ git add . && git commit -m "管理者ユーザーの仕組みを導入"

Counter: 430 (from 2010/06/03), today: 2, yesterday: 0