Authorization and resource boundary
Authentication identifies the user; authorization decides which guild or resource they may change. GuildGate leaves that decision to the application during action execution.
Resolve the resource on the server
A guildId from route parameters or the request body is only a candidate. Verify on the server that the user belongs to the Discord guild and holds the required permission.
resource: (input) => ({ type: "guild", id: input.guildId }),
authorize: async (context, input) => {
if (!context.userId) {
return { allowed: false, code: "session_required" };
}
const access = await discordAccess.resolveGuildAccess({
userId: context.userId,
guildId: input.guildId,
});
return access.canManage
? { allowed: true }
: { allowed: false, code: "missing_manage_guild" };
},
Never treat client-provided isAdmin, owner, role, or permission fields as an authority source.
Separate user and bot permissions
An action may have two distinct requirements:
- the user may change the setting
- the bot may perform the Discord operation
Even when the user is authorized, do not start an operation when the bot role is below the target role. Evaluate Discord permission bits as BigInt.
Refresh authorization at write time
The guild list collected during OAuth may be cached for fast UI rendering, but the security decision should use current data at action time. When caching access data, use a short TTL, explicit invalidation, and permission-loss tests.
Owner policy
The configured owners may change maintenance state or write block policies. An owner still:
- passes origin checks
- satisfies session and CSRF requirements
- is rate limited
- creates audit records
Owner status does not automatically inherit another user's normal action permissions. When such behavior is required, add an explicit and auditable rule to the action.
Maintenance and blocks
await gate.owner.setMaintenance({
enabled: true,
reason: "database migration",
allowOwners: true,
});
await gate.owner.block({
subjectType: "guild",
subjectId: guildId,
reason: "abuse investigation",
});
await gate.owner.unblock("guild", guildId);
A block subject may be user, guild, or ip. Hash or narrow IP data according to the audit and logging policy; avoid retaining unnecessary raw personal data.
Permission loss and realtime
Channel access is checked when a realtime subscription is opened. Revalidate session and channel access periodically for long-lived connections. Broadcast session revocation across workers and close matching connections.
Test matrix
Test at least these cases for every guarded action:
- no session
- expired session
- invalid origin or CSRF
- user is not a guild member
- insufficient user permission
- insufficient bot permission
- permission removed immediately before the write
- maintenance mode or active block
- owner and normal-user paths