# Permissions in a handler

Check permissions inside a handler with assertPermissions() and the gateway helpers. Covers asserting a bitfield, refusing a permission the target holds, rewording the refusal, and checking a member, a role, or your own bot.

A permission gate checks the caller and your bot before `execute()` starts. `/ban @someone` needs the target checked too. You get the target from the options inside `execute()`, after every gate has run. By hand, that check means comparing bitfields and writing a refusal message that lists what's missing.

The helpers on this page do that comparison inside your handler and throw the same refusal card a gate would. [`assertPermissions`](https://docs.seedcord.org/packages/core/latest/assert-permissions) runs on both transports. [`checkPermissions`](https://docs.seedcord.org/packages/gateway/latest/check-permissions) and [`checkBotPermissions`](https://docs.seedcord.org/packages/gateway/latest/check-bot-permissions) take discord.js objects, so they are gateway only.

## Asserting a bitfield

`assertPermissions` takes one object. It returns when the subject holds every bit in `scope`. Otherwise it throws a [`Notice`](https://docs.seedcord.org/packages/core/latest/notice) that lists what's missing.

{/* prettier-ignore-start */}

| field             | type                        | holds                                                                                                                    |
| ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `subject`         | `string`                    | who is checked, printed at the start of the refusal                                                                      |
| `permissions`     | `bigint`                    | the permission bits the subject holds, which you compute                                                                 |
| `scope`           | `readonly bigint[]`         | the `PermissionFlagsBits` values to check                                                                                |
| `inverse`         | `boolean`, optional         | refuses when the subject holds a bit in `scope`, see [refusing a permission they hold](#refusing-a-permission-they-hold) |
| `missingNotice`   | `Notice` subclass, optional | replaces the refusal for missing bits, see [replacing the refusal card](#replacing-the-refusal-card)                     |
| `dangerousNotice` | `Notice` subclass, optional | replaces the `inverse` refusal                                                                                           |

{/* prettier-ignore-end */}

You compute `permissions` yourself, so `assertPermissions` works on either transport. Here `/maintenance` checks that the person it is about to notify can see the channel.

```ts title="src/handlers/Maintenance.ts"
import {
    assertPermissions,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';
import { PermissionFlagsBits } from 'discord.js';

@SlashRoute('maintenance')
export class Maintenance extends SlashHandler<'maintenance'> {
    public async execute(): Promise<void> {
        const notify = this.options.getMember('notify');
        const channel = this.options.getChannel('target');

        if (notify === null) {
            await this.reply('That user is not in this server.');
            return;
        }

        assertPermissions({
            subject: `<@${notify.id}>`,
            permissions: channel.permissionsFor(notify, true)
                .bitfield,
            scope: [PermissionFlagsBits.ViewChannel]
        });

        await this.reply('Notified.');
    }
}
```

If the notified member can't see the channel, the default refusal card starts with `subject` and lists what's missing.

```txt output
### Cannot Proceed
<@123456789> is missing the following permission entries:

• View Channel
```

`subject` goes into the card exactly as you pass it. To show a mention there, pass `<@id>` for a member or `<@&id>` for a role.

`getMember` can return `null` even for a required user option. It reads the member from the interaction Discord sends. Discord leaves the member out in two cases.

* The command ran in a direct message.
* The chosen user isn't a member of the server.

> **Gateway and http differ**
>
> `getMember` returns a different shape on each transport, and you read the bits differently.
>
> {/* prettier-ignore-start */}
>
> | transport | `getMember` returns                     | the bits                      |
> | --------- | --------------------------------------- | ----------------------------- |
> | gateway   | discord.js `GuildMember`                | `member.permissions.bitfield` |
> | http      | `APIInteractionDataResolvedGuildMember` | `BigInt(member.permissions)`  |
>
> {/* prettier-ignore-end */}
>
> Both shapes carry the target's permissions, so `assertPermissions` works on either without a REST call. On http, Discord computes those bits for the channel the command ran in.

## Refusing a permission they hold

`inverse: true` flips the check. It throws when the subject holds any bit in `scope`, and returns otherwise.

Use it when the target must not hold a permission. `banMember` checks the target before it bans them.

```ts
async function banMember(target: GuildMember): Promise<void> {
    assertPermissions({
        subject: `<@${target.id}>`,
        permissions: target.permissions.bitfield,
        scope: [PermissionFlagsBits.Administrator],
        inverse: true
    });

    await target.ban();
}
```

If the target holds Administrator, `assertPermissions` throws before `target.ban()` runs.

> **Warning**
>
> The Administrator rule from [permissions](/checks/permissions) applies here too. The inverse check reports every bit in `scope` as present for anyone holding Administrator. Put only the bits you mean in `scope`, and read the refusal as "holds at least one of these".

## Replacing the refusal card

The default card says which permissions are missing. When your command needs its own wording, like "You can't ban a moderator", pass your own card. Each of the two refusals has its own override, and each takes a `Notice` subclass.

* `missingNotice` replaces the refusal for missing permissions.
* `dangerousNotice` replaces the `inverse: true` refusal for permissions the subject holds.

Both constructors receive the same three arguments: a message, the `subject` string, and the permission names. seedcord passes `undefined` as the message, so give your class its own wording.

[Throwing](/replying/throwing) shows how to write one.

```ts
assertPermissions({
    subject: `<@${target.id}>`,
    permissions: target.permissions.bitfield,
    scope: [PermissionFlagsBits.BanMembers],
    missingNotice: CannotBan
});
```

## Checking a role or member

On gateway you already hold a discord.js member or role, so you can skip reading its bitfield and writing its mention. [`checkPermissions`](https://docs.seedcord.org/packages/gateway/latest/check-permissions) computes the bitfield, builds `subject` as `<@id>` or `<@&id>`, and calls `assertPermissions` with both. What it computes depends on the second argument.

{/* prettier-ignore-start */}

| second argument | reads                                                               |
| --------------- | ------------------------------------------------------------------- |
| a `Guild`       | the base bits from the target's roles, before any channel overwrite |
| a `TextChannel` | the set that channel's overwrites resolve to                        |

{/* prettier-ignore-end */}

Pass a `Guild` for a server-wide permission like Kick Members. Pass a channel for a permission that channel overwrites can change, like Send Messages. `checkPermissions` has three call shapes: the guild form, the channel form, and an options object.

```ts
checkPermissions(member, guild, [PermissionFlagsBits.KickMembers]);

checkPermissions(member, channel, [
    PermissionFlagsBits.SendMessages
]);

checkPermissions(
    member,
    guild,
    [PermissionFlagsBits.Administrator],
    true
);

checkPermissions({
    for: member,
    in: channel,
    scope: [PermissionFlagsBits.SendMessages],
    inverse: true
});
```

A positional call takes `inverse` fourth and an object of notice overrides fifth. The object form takes them as fields: `inverse`, `missingNotice`, and `dangerousNotice`.

## Checking your own bot

A gate checks your bot's permissions in the channel where the command ran. If your handler posts in a different channel, check that channel. [`checkBotPermissions`](https://docs.seedcord.org/packages/gateway/latest/check-bot-permissions) runs the same check on your bot's member. It takes the guild or the channel, without a target.

```ts
checkBotPermissions(channel, [
    PermissionFlagsBits.SendMessages,
    PermissionFlagsBits.ViewChannel
]);
```

Its arguments after `scope` match `checkPermissions`, with `inverse` third and an object of notice overrides fourth.

If your bot's member isn't cached, the refusal lists every permission in `scope` as missing. The channel form also logs a warning on the `errors` log channel.

A passing check for Manage Roles still leaves role position to check. Discord rejects a role your bot assigns if that role is at or above the bot's highest role. [Changing roles](/checks/changing-roles) covers that check.
