├── LICENSE ├── auth-abac.ts └── auth-rbac.ts /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 WebDevSimplified 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /auth-abac.ts: -------------------------------------------------------------------------------- 1 | type Comment = { 2 | id: string 3 | body: string 4 | authorId: string 5 | createdAt: Date 6 | } 7 | 8 | type Todo = { 9 | id: string 10 | title: string 11 | userId: string 12 | completed: boolean 13 | invitedUsers: string[] 14 | } 15 | 16 | type Role = "admin" | "moderator" | "user" 17 | type User = { blockedBy: string[]; roles: Role[]; id: string } 18 | 19 | type PermissionCheck = 20 | | boolean 21 | | ((user: User, data: Permissions[Key]["dataType"]) => boolean) 22 | 23 | type RolesWithPermissions = { 24 | [R in Role]: Partial<{ 25 | [Key in keyof Permissions]: Partial<{ 26 | [Action in Permissions[Key]["action"]]: PermissionCheck 27 | }> 28 | }> 29 | } 30 | 31 | type Permissions = { 32 | comments: { 33 | dataType: Comment 34 | action: "view" | "create" | "update" 35 | } 36 | todos: { 37 | // Can do something like Pick to get just the rows you use 38 | dataType: Todo 39 | action: "view" | "create" | "update" | "delete" 40 | } 41 | } 42 | 43 | const ROLES = { 44 | admin: { 45 | comments: { 46 | view: true, 47 | create: true, 48 | update: true, 49 | }, 50 | todos: { 51 | view: true, 52 | create: true, 53 | update: true, 54 | delete: true, 55 | }, 56 | }, 57 | moderator: { 58 | comments: { 59 | view: true, 60 | create: true, 61 | update: true, 62 | }, 63 | todos: { 64 | view: true, 65 | create: true, 66 | update: true, 67 | delete: (user, todo) => todo.completed, 68 | }, 69 | }, 70 | user: { 71 | comments: { 72 | view: (user, comment) => !user.blockedBy.includes(comment.authorId), 73 | create: true, 74 | update: (user, comment) => comment.authorId === user.id, 75 | }, 76 | todos: { 77 | view: (user, todo) => !user.blockedBy.includes(todo.userId), 78 | create: true, 79 | update: (user, todo) => 80 | todo.userId === user.id || todo.invitedUsers.includes(user.id), 81 | delete: (user, todo) => 82 | (todo.userId === user.id || todo.invitedUsers.includes(user.id)) && 83 | todo.completed, 84 | }, 85 | }, 86 | } as const satisfies RolesWithPermissions 87 | 88 | export function hasPermission( 89 | user: User, 90 | resource: Resource, 91 | action: Permissions[Resource]["action"], 92 | data?: Permissions[Resource]["dataType"] 93 | ) { 94 | return user.roles.some(role => { 95 | const permission = (ROLES as RolesWithPermissions)[role][resource]?.[action] 96 | if (permission == null) return false 97 | 98 | if (typeof permission === "boolean") return permission 99 | return data != null && permission(user, data) 100 | }) 101 | } 102 | 103 | // USAGE: 104 | const user: User = { blockedBy: ["2"], id: "1", roles: ["user"] } 105 | const todo: Todo = { 106 | completed: false, 107 | id: "3", 108 | invitedUsers: [], 109 | title: "Test Todo", 110 | userId: "1", 111 | } 112 | 113 | // Can create a comment 114 | hasPermission(user, "comments", "create") 115 | 116 | // Can view the `todo` Todo 117 | hasPermission(user, "todos", "view", todo) 118 | 119 | // Can view all todos 120 | hasPermission(user, "todos", "view") 121 | -------------------------------------------------------------------------------- /auth-rbac.ts: -------------------------------------------------------------------------------- 1 | export type User = { roles: Role[]; id: string } 2 | 3 | type Role = keyof typeof ROLES 4 | type Permission = (typeof ROLES)[Role][number] 5 | 6 | const ROLES = { 7 | admin: [ 8 | "view:comments", 9 | "create:comments", 10 | "update:comments", 11 | "delete:comments", 12 | ], 13 | moderator: ["view:comments", "create:comments", "delete:comments"], 14 | user: ["view:comments", "create:comments"], 15 | } as const 16 | 17 | export function hasPermission(user: User, permission: Permission) { 18 | return user.roles.some(role => 19 | (ROLES[role] as readonly Permission[]).includes(permission) 20 | ) 21 | } 22 | 23 | // USAGE: 24 | const user: User = { id: "1", roles: ["user"] } 25 | 26 | // Can create a comment 27 | hasPermission(user, "create:comments") 28 | 29 | // Can view all comments 30 | hasPermission(user, "view:comments") 31 | --------------------------------------------------------------------------------