管理者権限を付与する の履歴(No.1)
更新権限を管理する†
AuthUser と 多対多 関係を持つ Role を導入する。
prisma/schema.prisma
model AuthUser {
...
+ roles Role[]
}
+ model Role {
+ id String @id @default(uuid())
+ name String @unique
+
+ users AuthUser[]
+ }
マイグレーションする。
LANG: console $ npx prisma migrate dev --name "add Role"
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.authUser.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.authUser.update({
where: { id: userId },
data: {
roles: {
connectOrCreate: roles.map(role=>
({where: {name: role}, create: {name: role}})
)
},
},
})
}
async removeRoles(userId: string, ...roles: string[]) {
await this.authUser.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/(loggedOut)/signup/+page.server.ts
+ import { db } from '$lib/server/db'
...
+ // 最初のユーザーには admin 権限を持たせる
+ if(await db.authUser.count() == 1) {
+ await db.addRoles(user.userId, 'admin')
+ }
/(admin) あるいは /admin から始まるパスには admin 権限を持っているユーザーしかアクセスできない。
src/hooks.server.ts
LANG:ts
+ if (event.route.id?.startsWith('/(admin)') || event.route.id?.startsWith('/admin')) {
+ if (!await db.hasRole(session?.user?.userId, 'admin')) {
+ return Response.redirect(`${event.url.origin}/`, 302);
+ }
+ }
Counter: 771 (from 2010/06/03),
today: 1,
yesterday: 0