From 89f190f18bacd5b81f0f4997698a56ad0afea5d4 Mon Sep 17 00:00:00 2001 From: Vladyslav Halatskyi Date: Mon, 22 Jul 2024 17:23:35 +0300 Subject: [PATCH] feat: Enhanced permission system for character/account with messenger support. (#111) * feat: Enhanced permission system. * Refactored docs according to new format --- docs/useRebar/document/document-account.md | 178 +++++++++++++--- docs/useRebar/document/document-character.md | 162 ++++++++++----- src/main/server/document/account.ts | 92 ++++----- src/main/server/document/character.ts | 201 ++++++------------- src/main/server/systems/messenger.ts | 69 +++++-- src/main/server/systems/permissionGroup.ts | 25 +++ src/main/server/systems/permissionProxy.ts | 200 ++++++++++++++++++ 7 files changed, 636 insertions(+), 291 deletions(-) create mode 100644 src/main/server/systems/permissionProxy.ts diff --git a/docs/useRebar/document/document-account.md b/docs/useRebar/document/document-account.md index a4527b6b8..6c4ede14d 100644 --- a/docs/useRebar/document/document-account.md +++ b/docs/useRebar/document/document-account.md @@ -44,16 +44,6 @@ const data = account.get(); console.log(data.email); ``` -### addPermission - -Add a permission to the account. - -```ts -const document = Rebar.document.account.useAccount(player); - -await document.permission.addPermission('admin'); -``` - ### checkPassword When you setup an account you often want to also setup a password, or check a password. @@ -94,19 +84,6 @@ async function someFunction(player: alt.Player) { } ``` -### hasPermission - -```ts -async function someFunction(player: alt.Player) { - const document = Rebar.document.account.useAccount(player); - - if (!document.hasPermission('admin')) { - // No Permission - return; - } -} -``` - ### isValid If you need to check if a player has an account document bound to them, you can use the following method. @@ -140,15 +117,6 @@ async function someFunction(player: alt.Player) { } ``` -### removePermission - -```ts -async function someFunction(player: alt.Player) { - const document = Rebar.document.account.useAccount(player); - await document.permission.removePermission('admin'); -} -``` - ### setBanned Banning an account is pretty straight forward but it does not prevent new accounts with new ips. @@ -186,6 +154,152 @@ function someFunction(player: alt.Player) { } ``` +### permissions + +You can grant/revoke permissions to the account and check if account has them. + +#### addPermission + +Grants a permission to the account. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + await document.permissions.addPermission('admin'); +} +``` + +#### removePermission + +Revokes permission from the account. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + await document.permissions.removePermission('admin'); +} +``` + +#### hasPermission + +Checks if account has a permission. + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const hasPerm: boolean = document.permissions.hasPermission('admin'); +} +``` + +#### hasAllPermissions + +Checks if account has all of the provided permissions: + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const hasAllPerm: boolean = document.permissions.hasAllPermissions(['admin', 'support']); +} +``` + +#### hasAnyPermission + +Checks if account has at least one of the provided permissions: + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const hasAnyPerm: boolean = document.permissions.hasAnyPermission(['admin', 'support']); +} +``` + +### groupPermissions + +Permission groups allow you to assign permissions under a specific group name for an account. + +#### addPermissions + +Adds permission to specified group. If an account had no group before - it will create it. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + await document.groupPermissions.addPermissions('admin', 'noclip'); +} +``` + +#### removePermissions + +Removes permission from group, it will also remove a group, if there are no permissions left. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + await document.groupPermissions.removePermissions('admin', 'noclip'); +} +``` + +#### removeGroup + +Completely removes group from an account with all its permissions. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + await document.groupPermissions.removeGroup('admin'); +} +``` + +#### hasGroup + +Checks if account belongs to group. + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const isAdmin: boolean = document.groupPermissions.hasGroup('admin'); +} +``` + +#### hasGroupPerm + +Checks if account belongs to group and has a specific permission. + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const canNoclip: boolean = document.groupPermissions.hasGroupPerm('admin', 'noclip'); +} +``` + +#### hasAtLeastOneGroupPerm + +Checks if account belongs to group and has any of provided permissions. + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const canBanOrNoclip: boolean = document.groupPermissions.hasAtLeastOneGroupPerm('admin', ['noclip', 'ban']); +} +``` + +#### hasAtLeastOneGroupWithSpecificPerm + +Checks if account belongs to any of the group and has corresponding rights. + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.account.useAccount(player); + const isStaff = document.groupPermissions.hasAtLeastOneGroupWithSpecificPerm({ + admin: ['noclip', 'ban'], + support: ['answerReport'] + }); + // This will be true if: + // 1. Account has `admin` group with `noclip` OR/AND `ban` permission. + // 2. Account has `support` group with `answerReport` permission. +} +``` + ## useAccountEvents Listen for individual key changes for a given document. diff --git a/docs/useRebar/document/document-character.md b/docs/useRebar/document/document-character.md index d3e1e923e..48b9864e6 100644 --- a/docs/useRebar/document/document-character.md +++ b/docs/useRebar/document/document-character.md @@ -29,35 +29,13 @@ const someCharacterData = { }; // Bind account data to the player after fetching -const document = Rebar.document.character.useAccountBinder(player).bind(someCharacterData); +const document = Rebar.document.character.useCharacterBinder(player).bind(someCharacterData); ``` --- ## useCharacter -### addGroupPerm - -Add a permission group with a specific ranking. - -```ts -function someFunction(player: alt.Player) { - const document = Rebar.document.character.useCharacter(player); - document.addGroupPerm('police', 'cadet'); -} -``` - -### addPermission - -Add a permission to the character. - -```ts -function someFunction(player: alt.Player) { - const document = Rebar.document.character.useCharacter(player); - document.permission.addPermission('mechanic'); -} -``` - ### isValid If you need to check if a player has a document bound to them, you can use the following method. @@ -109,85 +87,169 @@ async function someFunction(player: alt.Player) { } ``` -### hasPermission +### set -Add a permission to the character. +Set a single field to be stored in the database. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - if (!document.permission.hasPermission('mechanic')) { - // No Permission! - return; - } + document.set('banned', true); +} +``` + +### setBulk + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + document.setBulk({ name: 'New_Name!', health: 200 }); +} +``` + +### permissions + +You can grant/revoke permissions to the character and check if character has them. + +#### addPermission + +Grants a permission to the character. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + await document.permissions.addPermission('admin'); +} +``` + +#### removePermission + +Revokes permission from the character. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + await document.permissions.removePermission('admin'); } ``` -### hasAnyGroupPermission +#### hasPermission -Check if the character has any group permission. +Checks if character has a permission. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - if (!document.hasAnyGroupPermission('police', ['cadet'])) { - return; - } + const hasPerm: boolean = document.permissions.hasPermission('admin'); } ``` -### hasGroupPerm +#### hasAllPermissions -Check if the character has a specific group perm. +Checks if character has all of the provided permissions: ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - if (!document.hasGroupPerm('police', 'cadet')) { - return; - } + const hasAllPerm: boolean = document.permissions.hasAllPermissions(['admin', 'support']); +} +``` + +#### hasAnyPermission + +Checks if character has at least one of the provided permissions: + +```ts +function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + const hasAnyPerm: boolean = document.permissions.hasAnyPermission(['admin', 'support']); +} +``` + +### groupPermissions + +Permission groups allow you to assign permissions under a specific group name for an character. + +#### addPermissions + +Adds permission to specified group. If an character had no group before - it will create it. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + await document.groupPermissions.addPermissions('admin', 'noclip'); +} +``` + +#### removePermissions + +Removes permission from group, it will also remove a group, if there are no permissions left. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + await document.groupPermissions.removePermissions('admin', 'noclip'); } ``` -### removeGroupPerm +#### removeGroup -Add a permission group with a specific ranking. +Completely removes group from an character with all its permissions. + +```ts +async function someFunction(player: alt.Player) { + const document = Rebar.document.character.useCharacter(player); + await document.groupPermissions.removeGroup('admin'); +} +``` + +#### hasGroup + +Checks if character belongs to group. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - document.removeGroupPerm('police', 'cadet'); + const isAdmin: boolean = document.groupPermissions.hasGroup('admin'); } ``` -### removePermission +#### hasGroupPerm -Add a permission to the character. +Checks if character belongs to group and has a specific permission. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - document.permission.removePermission('mechanic'); + const canNoclip: boolean = document.groupPermissions.hasGroupPerm('admin', 'noclip'); } ``` -### set +#### hasAtLeastOneGroupPerm -Set a single field to be stored in the database. +Checks if character belongs to group and has any of provided permissions. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - document.set('banned', true); + const canBanOrNoclip: boolean = document.groupPermissions.hasAtLeastOneGroupPerm('admin', ['noclip', 'ban']); } ``` -### setBulk +#### hasAtLeastOneGroupWithSpecificPerm + +Checks if character belongs to any of the group and has corresponding rights. ```ts function someFunction(player: alt.Player) { const document = Rebar.document.character.useCharacter(player); - document.setBulk({ name: 'New_Name!', health: 200 }); + const isStaff = document.groupPermissions.hasAtLeastOneGroupWithSpecificPerm({ + admin: ['noclip', 'ban'], + support: ['answerReport'] + }); + // This will be true if: + // 1. Character has `admin` group with `noclip` OR/AND `ban` permission. + // 2. Character has `support` group with `answerReport` permission. } ``` diff --git a/src/main/server/document/account.ts b/src/main/server/document/account.ts index 3a82c7c25..47e0ecc91 100644 --- a/src/main/server/document/account.ts +++ b/src/main/server/document/account.ts @@ -5,8 +5,8 @@ import { KnownKeys } from '@Shared/utilityTypes/index.js'; import { useDatabase } from '@Server/database/index.js'; import { CollectionNames, KeyChangeCallback } from './shared.js'; import { Character } from '@Shared/types/character.js'; -import { usePermission } from '@Server/systems/permission.js'; import { useRebar } from '../index.js'; +import { usePermissionProxy } from '@Server/systems/permissionProxy.js'; const Rebar = useRebar(); const sessionKey = 'document:account'; @@ -149,51 +149,6 @@ export function useAccount(player: alt.Player) { return results as (Character & T)[]; } - /** - * Add a permission to the given player's account. - * - * @export - * @param {alt.Player} player - * @param {string} permission - * @return {*} - */ - async function addPermission(permission: string) { - if (!player.valid) { - return false; - } - - const perm = usePermission(player); - return await perm.add('account', permission); - } - - /** - * Remove a permission to the given player's account. - * - * @export - * @param {string} permission - * @return {*} - */ - async function removePermission(permission: string) { - if (!player.valid) { - return false; - } - - const perm = usePermission(player); - return await perm.remove('account', permission); - } - - /** - * Check if the player has an account permission. - * - * @export - * @param {string} permission - * @return {boolean} - */ - function hasPermission(permission: string) { - const perm = usePermission(player); - return perm.has('account', permission); - } - /** * Set the password for this account * @@ -240,26 +195,57 @@ export function useAccount(player: alt.Player) { await setBulk({ id }); return id; } + const { permissions, groupPermissions } = usePermissionProxy(player, 'account', get, set); + /** + * Old permission system. Will be deprecated. + * @deprecated + */ const permission = { - addPermission, - removePermission, - hasPermission, - setBanned, - }; + /** + * @deprecated + */ + addPermission: async (permissionName: string) => { + alt.logWarning('Consider using useAccount(...).permissions.addPermission instead. This will be deprecated.'); + return permissions.addPermission(permissionName); + }, + /** + * @deprecated + */ + removePermission: async (permissionName: string) => { + alt.logWarning('Consider using useAccount(...).permissions.removePermission instead. This will be deprecated.'); + return permissions.removePermission(permissionName); + }, + /** + * @deprecated + */ + hasPermission: (permissionName: string) => { + alt.logWarning('Consider using useAccount(...).permissions.hasPermission instead. This will be deprecated.'); + return permissions.hasPermission(permissionName); + }, + /** + * @deprecated + */ + setBanned: async (reason: string) => { + alt.logWarning('Consider using useAccount(...).setBanned instead. This will be deprecated.'); + return setBanned(reason); + }, + } return { + permission, addIdentifier, - addPermission, get, getCharacters, getField, isValid, - permission, set, setBulk, setPassword, checkPassword, + setBanned, + permissions, + groupPermissions, }; } diff --git a/src/main/server/document/character.ts b/src/main/server/document/character.ts index 65edaa60d..073f09954 100644 --- a/src/main/server/document/character.ts +++ b/src/main/server/document/character.ts @@ -4,9 +4,9 @@ import { KnownKeys } from '@Shared/utilityTypes/index.js'; import { useDatabase } from '@Server/database/index.js'; import { CollectionNames, KeyChangeCallback } from './shared.js'; import { Vehicle } from 'main/shared/types/vehicle.js'; -import { usePermission } from '@Server/systems/permission.js'; -import { usePermissionGroup } from '@Server/systems/permissionGroup.js'; import { useRebar } from '../index.js'; +import { usePermissionProxy } from '@Server/systems/permissionProxy.js'; +import { removeGroup } from 'natives'; const Rebar = useRebar(); const sessionKey = 'document:character'; @@ -151,156 +151,77 @@ export function useCharacter(player: alt.Player) { return results as (Vehicle & T)[]; } - /** - * Adds a permission to this character - * - * @async - * @name addPermission - * @param {string} permission - * @returns {Promise} - * @exports - */ - async function addPermission(permission: string) { - if (!player.valid) { - return false; - } - - const perm = usePermission(player); - return await perm.add('character', permission); - } - - /** - * Removes a permission from the given player character. - * - * @async - * @name removePermission - * @param {string} permission - * @returns {Promise} - * @exports - */ - async function removePermission(permission: string) { - if (!player.valid) { - return false; - } - - const perm = usePermission(player); - return await perm.remove('character', permission); - } - - /** - * Check if the current player character has a permission. - * - * @export - * @param {string} permission - * @return {boolean} - */ - function hasPermission(permission: string) { - if (!player.valid) { - return false; - } - - const perm = usePermission(player); - return perm.has('character', permission); - } - - /** - * Check if a player character has a group permission. - * - * @export - * @param {string} groupName - * @param {string} permission - * @returns {boolean} - */ - function hasGroupPermission(groupName: string, permission: string) { - const data = get(); - if (typeof data === 'undefined') { - return false; - } - - const perm = usePermissionGroup(data); - return perm.hasGroupPerm(groupName, permission); - } - - /** - * Check if a player has any matching permission - * - * @export - * @param {PermissionGroup} document - * @param {string} groupName - * @param {string} permission - */ - function hasAnyGroupPermission(groupName: string, permissions: string[]) { - const data = get(); - if (typeof data === 'undefined') { - return false; - } - - const perm = usePermissionGroup(data); - return perm.hasAtLeastOneGroupPerm(groupName, permissions); - } - - /** - * Add a group permission to a character. - * - * @export - * @param {string} groupName - * @param {string} permission - * @return {Promise} - */ - async function addGroupPerm(groupName: string, permission: string): Promise { - const data = get(); - if (typeof data === 'undefined') { - return false; - } - - const perm = usePermissionGroup(data); - const newDocument = perm.addGroupPerm(groupName, permission); - await set('groups', newDocument.groups); - return true; - } - - /** - * Remove a group permission from a character. - * - * @export - * @param {string} groupName - * @param {string} permission - * @return {Promise} - */ - async function removeGroupPerm(groupName: string, permission: string): Promise { - const data = get(); - if (typeof data === 'undefined') { - return false; - } - - const perm = usePermissionGroup(data); - const newDocument = perm.removeGroupPerm(groupName, permission); - await set('groups', newDocument.groups); - return true; - } - async function addIdentifier() { if (typeof getField('id') !== 'undefined') { return getField('id'); } - const identifier = await Rebar.database.useIncrementalId(Rebar.database.CollectionNames.Characters); + const identifier = Rebar.database.useIncrementalId(Rebar.database.CollectionNames.Characters); const id = await identifier.getNext(); await setBulk({ id }); return id; } + const { permissions, groupPermissions } = usePermissionProxy(player, 'character', get, set); + + /** + * Old permission system. Will be deprecated in the future. + * Use the new permission system instead. + * @deprecated + */ const permission = { - addPermission, - addGroupPerm, - removePermission, - removeGroupPerm, - hasAnyGroupPermission, - hasPermission, - hasGroupPermission, - }; + /** + * @deprecated + */ + addPermission: async (permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.addPermission. This will be deprecated.'); + return await permissions.addPermission(permissionName); + }, + /** + * @deprecated + */ + removePermission: async (permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.removePermission. This will be deprecated.'); + return await permissions.removePermission(permissionName); + }, + /** + * @deprecated + */ + hasPermission: (permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.hasPermission. This will be deprecated.'); + return permissions.hasPermission(permissionName); + }, + /** + * @deprecated + */ + hasGroupPermission: (groupName: string, permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.hasGroupPermission. This will be deprecated.'); + return groupPermissions.hasGroupPerm(groupName, permissionName); + }, + /** + * @deprecated + */ + hasAnyGroupPermission: (groupName: string, permissionNames: string[]) => { + alt.logWarning('Consider using useCharacter(...).permissions.hasAnyGroupPermission. This will be deprecated.'); + return groupPermissions.hasAtLeastOneGroupPerm(groupName, permissionNames); + }, + /** + * @deprecated + */ + addGroupPerm: async (groupName: string, permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.addGroupPerm. This will be deprecated.'); + return await groupPermissions.addPermissions(groupName, [permissionName]); + }, + /** + * @deprecated + */ + removeGroupPerm: async (groupName: string, permissionName: string) => { + alt.logWarning('Consider using useCharacter(...).permissions.removeGroupPerm. This will be deprecated.'); + return await groupPermissions.removePermissions(groupName, [permissionName]); + } + } - return { addIdentifier, get, getField, isValid, getVehicles, permission, set, setBulk }; + return { addIdentifier, get, getField, isValid, getVehicles, permission, permissions, groupPermissions, set, setBulk }; } export function useCharacterBinder(player: alt.Player, syncPlayer = true) { diff --git a/src/main/server/systems/messenger.ts b/src/main/server/systems/messenger.ts index 4523c78ff..0e0e3ad83 100644 --- a/src/main/server/systems/messenger.ts +++ b/src/main/server/systems/messenger.ts @@ -5,15 +5,17 @@ import { Message } from '../../shared/types/message.js'; export type PlayerMessageCallback = (player: alt.Player, msg: string) => void; -export type CommandOptions = { +export type PermissionOptions = { permissions?: string[]; accountPermissions?: string[]; + groups?: Record; + accountGroups?: Record; }; export type Command = { name: string; desc: string; - options?: CommandOptions; + options?: PermissionOptions; callback: (player: alt.Player, ...args: string[]) => void | boolean | Promise | Promise; }; @@ -42,6 +44,45 @@ const commands: Command[] = []; const callbacks: PlayerMessageCallback[] = []; let endCommandRegistrationTime = Date.now(); +/** + * + * @param {alt.Player} player Player to check permissions for. + * @param {PermissionOptions} options Permission options to check against. + * @returns {boolean} True if the player has access to the functionality. + */ +function isAvailableForPlayer(player: alt.Player, options?: PermissionOptions): boolean { + if (!player.valid) return false; + if (!options) return true; + if ( + !options.accountPermissions && + !options.permissions && + !options.groups && + !options.accountGroups + ) { + return true; + } + + const rPlayer = Rebar.usePlayer(player); + + if (rPlayer.account.groupPermissions.hasAtLeastOneGroupWithSpecificPerm(options.accountGroups)) { + return true; + } + + if (rPlayer.character.groupPermissions.hasAtLeastOneGroupWithSpecificPerm(options.groups)) { + return true; + } + + if (rPlayer.account.permissions.hasAnyPermission(options.accountPermissions)) { + return true; + } + + if (rPlayer.character.permissions.hasAnyPermission(options.permissions)) { + return true; + } + + return false; +} + export function useMessenger() { function registerCommand(command: Command) { command.name = command.name.replaceAll('/', ''); @@ -61,20 +102,7 @@ export function useMessenger() { } function hasCommandPermission(player: alt.Player, command: Command) { - const permissions = Rebar.permission.usePermission(player); - if (!command.options.accountPermissions && !command.options.permissions) { - return true; - } - - if (permissions.hasOne('account', command.options.accountPermissions)) { - return true; - } - - if (permissions.hasOne('character', command.options.permissions)) { - return true; - } - - return false; + return isAvailableForPlayer(player, command.options); } async function invokeCommand(player: alt.Player, cmdName: string, ...args: any[]): Promise { @@ -105,6 +133,14 @@ export function useMessenger() { webview.emit(Events.systems.messenger.send, message); } + function broadcastMessage(message: Message, options?: PermissionOptions) { + for (const player of alt.Player.all) { + if (isAvailableForPlayer(player, options)) { + sendMessage(player, message); + } + } + } + /** * Get all commands a player has access to, including permissioned commands. * @@ -140,6 +176,7 @@ export function useMessenger() { message: { on: onMessage, send: sendMessage, + broadcast: broadcastMessage, }, }; } diff --git a/src/main/server/systems/permissionGroup.ts b/src/main/server/systems/permissionGroup.ts index 42d2ec916..5341c1cf5 100644 --- a/src/main/server/systems/permissionGroup.ts +++ b/src/main/server/systems/permissionGroup.ts @@ -167,9 +167,34 @@ export function usePermissionGroup(document: T & PermissionGroup) { return false; } + function hasAtLeastOneGroupWithSpecificPerm(groups: Record): boolean { + if (!document.groups) { + return false; + } + + for (const groupName in groups) { + if (!groups.hasOwnProperty(groupName)) { + continue; + } + + if (!document.groups[groupName]) { + continue; + } + + for (const permission of groups[groupName]) { + if (hasGroupPerm(groupName, permission)) { + return true; + } + } + } + + return false; + } + return { addGroupPerm, hasAtLeastOneGroupPerm, + hasAtLeastOneGroupWithSpecificPerm, hasGroup, hasGroupPerm, removeGroup, diff --git a/src/main/server/systems/permissionProxy.ts b/src/main/server/systems/permissionProxy.ts new file mode 100644 index 000000000..528f601e0 --- /dev/null +++ b/src/main/server/systems/permissionProxy.ts @@ -0,0 +1,200 @@ +import * as alt from 'alt-server'; +import { usePermission } from './permission.js'; +import { usePermissionGroup } from './permissionGroup.js'; + +type DocumentType = 'character' | 'account'; +type DocumentGetter = () => Object | undefined; +type DocumentSetter = (fieldName: string, value: any) => Promise; + +/** + * Permission proxy for useCharacter/useAccount/useVehicle. + * + * @export + * @param {alt.Player} player + * @param {DocumentType} documentType Document type to handle permissions for. + * @param {DocumentGetter} getter Getter function to get the document. + * @param {DocumentSetter} setter Setter function to set the document. + */ +export function usePermissionProxy( + player: alt.Player, + documentType: DocumentType, + getter: DocumentGetter, + setter: DocumentSetter, +) { + const permissions = { + /** + * Proxies the add permission function. + * + * @param {string} permission + * @returns {Promise} + */ + addPermission: async (permission: string): Promise => { + if (!player.valid) return false; + const perm = usePermission(player); + return await perm.add(documentType, permission); + }, + + /** + * Proxies the remove permission function. + * + * @param {string} permission + * @returns {Promise} + */ + removePermission: async (permission: string): Promise => { + if (!player.valid) return false; + const perm = usePermission(player); + return await perm.remove(documentType, permission); + }, + + /** + * Proxies the has permission function. + * + * @param {string} permission + * @returns {boolean} + */ + hasPermission: (permission: string): boolean => { + if (!player.valid) return false; + const perm = usePermission(player); + return perm.has(documentType, permission); + }, + + /** + * Proxies the has all permission function. + * + * @param {string[]} permissions + * @returns {boolean} + */ + hasAllPermissions: (permissions: string[]): boolean => { + if (!player.valid) return false; + const perm = usePermission(player); + return perm.hasAll(documentType, permissions); + }, + + /** + * Proxies the has any permission function. + * + * @param {string[]} permissions + * @returns {boolean} + */ + hasAnyPermission: (permissions: string[]): boolean => { + if (!player.valid) return false; + const perm = usePermission(player); + return perm.hasOne(documentType, permissions); + }, + + /** + * Proxies the clear permissions function. + * + * @returns {void} + */ + clear: (): void => { + if (!player.valid) return; + const perm = usePermission(player); + perm.clear(documentType); + }, + }; + + const groupPermissions = { + /** + * Proxies the add group function. + * + * @param {string} groupName Group name to add. + * @param {string[]} permissions Permissions to add to group. + * @returns {Promise} Returns true if successful. + */ + addPermissions: async (groupName: string, permissions: string[] | string): Promise => { + const data = getter(); + if (typeof data === 'undefined') return false; + + if (typeof permissions === 'string') { + permissions = [permissions]; + } + + const perm = usePermissionGroup(data); + const updatedDocument = perm.addGroupPerm(groupName, permissions); + await setter('groups', updatedDocument.groups); + return true; + }, + /** + * Proxies the remove permission from group function. + * + * @param {string} groupName Group name to remove permission from. + * @param {string[]} permissions Permissions to remove from group. + * @returns {Promise} Returns true if successful. + */ + removePermissions: async (groupName: string, permissions: string[]): Promise => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + const updatedDocument = perm.removeGroupPerm(groupName, permissions); + await setter('groups', updatedDocument.groups); + return true; + }, + /** + * Proxies the remove group function. + * + * @param {string} groupName Group name to remove. + * @returns {Promise} Returns true if successful. + */ + removeGroup: async (groupName: string): Promise => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + const updatedDocument = perm.removeGroup(groupName); + await setter('groups', updatedDocument.groups); + return true; + }, + /** + * Proxies the has group function. + * + * @param {string} groupName Group name to check. + * @returns {boolean} Returns true if the group exists. + */ + hasGroup: (groupName: string): boolean => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + return perm.hasGroup(groupName); + }, + /** + * Proxies the has group permission function. + * + * @param {string} groupName Group name to check. + * @param {string} permission Permission to check. + * @returns {boolean} Returns true if the group has the permission. + */ + hasGroupPerm: (groupName: string, permission: string): boolean => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + return perm.hasGroupPerm(groupName, permission); + }, + /** + * Proxies the has at least one group permission function. + * + * @param {string} groupName Group name to check. + * @param {string[]} permissions Permissions to check. + * @returns {boolean} Returns true if the group has at least one permission. + */ + hasAtLeastOneGroupPerm: (groupName: string, permissions: string[]): boolean => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + return perm.hasAtLeastOneGroupPerm(groupName, permissions); + }, + /** + * Proxies the has at least one group with specific permission function. + * + * @param {Record} groups Groups to check. + * @returns {boolean} Returns true if at least one group has specific permission. + */ + hasAtLeastOneGroupWithSpecificPerm: (groups: Record): boolean => { + const data = getter(); + if (typeof data === 'undefined') return false; + const perm = usePermissionGroup(data); + return perm.hasAtLeastOneGroupWithSpecificPerm(groups); + }, + } + + return { permissions, groupPermissions }; +}