From: xangelo Date: Wed, 6 Sep 2023 18:54:41 +0000 (-0400) Subject: feat: rbac support with admin permission X-Git-Tag: v0.3.6~2 X-Git-Url: https://git.xangelo.ca/?a=commitdiff_plain;h=940079de64c7a0c8070f5a78e656fe62c4f05603;p=risinglegends.git feat: rbac support with admin permission There is now a permissions array on the player object loaded from `loadPlayer` that includes a `permissions` array that lets us check if the user has certain permissions. Support permissions: admin,moderator The first thing we did was remove the hard-coded key during chat resets --- diff --git a/migrations/20230906180935_rbac.ts b/migrations/20230906180935_rbac.ts new file mode 100644 index 0000000..e110d2d --- /dev/null +++ b/migrations/20230906180935_rbac.ts @@ -0,0 +1,18 @@ +import { Knex } from "knex"; + + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('permissions', function(table){ + table.string('name').primary(); + }).createTable('player_permissions', function(table) { + table.string('permission'); + table.uuid('player_id'); + table.primary(['permission', 'player_id']); + }); +} + + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('permissions').dropTable('player_permissions'); +} + diff --git a/src/server/api.ts b/src/server/api.ts index 80aa82a..a64809c 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -165,23 +165,23 @@ app.post('/chat', authEndpoint, async (req: Request, res: Response) => { } let message: Message; - if(msg.startsWith('/server lmnop')) { + if(msg.startsWith('/server') && req.player.permissions.includes('admin')) { try { - if(msg === '/server lmnop refresh-monsters') { + if(msg === '/server refresh-monsters') { await createMonsters(); message = broadcastMessage('server', 'Monster refresh!'); } - else if(msg === '/server lmnop refresh-cities') { + else if(msg === '/server refresh-cities') { await createAllCitiesAndLocations(); message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!'); } - else if(msg === '/server lmnop refresh-shops') { + else if(msg === '/server refresh-shops') { await createShopItems(); await createShopEquipment(); message = broadcastMessage('server', 'Refresh shop items'); } else { - const str = msg.split('/server lmnop ')[1]; + const str = msg.split('/server ')[1]; if(str) { message = broadcastMessage('server', str); } diff --git a/src/server/player.ts b/src/server/player.ts index 527069b..a9d077c 100644 --- a/src/server/player.ts +++ b/src/server/player.ts @@ -7,7 +7,12 @@ import {logger} from './lib/logger'; export async function loadPlayer(authToken: string): Promise { const res = await db.first() - .select('players.*', 'profession_levels.level', 'profession_levels.exp') + .select( + 'players.*', + 'profession_levels.level', + 'profession_levels.exp', + db.raw(`coalesce(pp.permissions, '[]'::json) as permissions`) + ) .from('players') .join('profession_levels', function() { this.on(function() { @@ -15,11 +20,15 @@ export async function loadPlayer(authToken: string): Promise { this.andOn('profession_levels.profession', '=', 'players.profession') }) }) + .leftJoin( + db.raw(`(select json_agg(pp.permission) as permissions, pp.player_id from player_permissions pp group by pp.player_id) pp`), + 'pp.player_id','=', 'players.id' + ) .where({ 'players.id': authToken }); - return res; + return res; } export async function createPlayer(): Promise { diff --git a/src/shared/player.ts b/src/shared/player.ts index b7c172a..93d75f3 100644 --- a/src/shared/player.ts +++ b/src/shared/player.ts @@ -3,6 +3,8 @@ import { Stat } from './stats'; import { SkillDefinition, Skill } from './skills'; import { EquippedItemDetails } from './equipped'; +export type Permission = 'admin' | 'moderator'; + export type Player = { id: string, account_type: 'session' | 'auth', @@ -21,6 +23,7 @@ export type Player = { city_id: number; stat_points: number; vigor: number; + permissions: Permission[] } export type PlayerWithSkills = Player & {