feat(platform): gate kick, ban, and bot install tokens behind step-up PIN

Wire pearcord-step-up into bootstrap and view snapshot (stepUpPinSet,
stepUpUnlocked). Add setStepUpPin, verifyStepUpPin, clearStepUpPin, and
_assertStepUp before moderation and bot-admin APIs. Depends on pearcord-step-up.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-21 21:24:24 -04:00
co-authored by Cursor
parent ab31c97441
commit 6dadc18c51
2 changed files with 63 additions and 0 deletions
+62
View File
@@ -20,6 +20,7 @@ const { PearcordEmojiRegistry } = require('pearcord-emoji')
const { BotEventFanout, BOT_EVENTS } = require('pearcord-bot-events')
const { INTENTS } = require('pearcord-bot-sdk')
const { BotRateLimits } = require('pearcord-bot-limits')
const { PearcordStepUp } = require('pearcord-step-up')
const {
createInstallToken,
verifyInstallToken,
@@ -192,6 +193,11 @@ class PearcordPlatform extends EventEmitter {
})
await this.userSettings.ready()
this.userSettings.on('prefs', () => this.emit('user-prefs'))
this.stepUp = new PearcordStepUp({
storagePath: this.storagePath,
userId: user.id
})
await this.stepUp.ready()
this.guilds = await this.listGuilds()
await this._joinDiscoveryMesh()
await this._joinContactsMesh()
@@ -1629,6 +1635,51 @@ class PearcordPlatform extends EventEmitter {
return bot
}
async _ensureStepUp () {
const user = this.identity?.user
if (!user) throw new Error('register first')
if (!this.stepUp) {
this.stepUp = new PearcordStepUp({
storagePath: this.storagePath,
userId: user.id
})
await this.stepUp.ready()
} else {
this.stepUp.setUserId(user.id)
}
return this.stepUp
}
async _assertStepUp () {
const stepUp = await this._ensureStepUp()
await stepUp.assertVerified()
}
async setStepUpPin (pin) {
const stepUp = await this._ensureStepUp()
return stepUp.setPin(pin)
}
async verifyStepUpPin (pin) {
const stepUp = await this._ensureStepUp()
const ok = await stepUp.verifyPin(pin)
if (!ok) throw new Error('incorrect PIN')
return { unlocked: true, until: stepUp.sessionExpiresAt() }
}
async clearStepUpPin () {
const stepUp = await this._ensureStepUp()
return stepUp.clearPin()
}
async getStepUpStatus () {
const stepUp = await this._ensureStepUp()
return {
pinSet: await stepUp.hasPin(),
unlocked: stepUp.isSessionActive()
}
}
async createBotInstallToken ({
name,
intents = INTENTS.GUILD_MESSAGES | INTENTS.MESSAGE_CONTENT,
@@ -1640,6 +1691,7 @@ class PearcordPlatform extends EventEmitter {
if (!this.guild?.guild) throw new Error('no guild')
const user = this.identity.user
if (!user) throw new Error('register first')
await this._assertStepUp()
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to create bot install tokens')
@@ -2027,6 +2079,7 @@ class PearcordPlatform extends EventEmitter {
async kickMember ({ userId, reason }) {
const user = this.identity.user
if (!user || !this.guild?.guild) throw new Error('no guild')
await this._assertStepUp()
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MODERATE_MEMBERS)) {
throw new Error('no permission to kick')
@@ -2053,6 +2106,7 @@ class PearcordPlatform extends EventEmitter {
async banMember ({ userId, reason }) {
const user = this.identity.user
if (!user || !this.guild?.guild) throw new Error('no guild')
await this._assertStepUp()
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MODERATE_MEMBERS)) {
throw new Error('no permission to ban')
@@ -2332,6 +2386,12 @@ class PearcordPlatform extends EventEmitter {
? await this.userSettings.getPrefs()
: null
const userPrefsStats = this.userSettings?.getStats?.() || null
let stepUpPinSet = false
let stepUpUnlocked = false
if (this.stepUp) {
stepUpPinSet = await this.stepUp.hasPin()
stepUpUnlocked = this.stepUp.isSessionActive()
}
let dmPeerReadAt = 0
let dmReadByUser = {}
if (this.mode === 'dm' && this.dm?.channel?.id) {
@@ -2418,6 +2478,8 @@ class PearcordPlatform extends EventEmitter {
notificationUnread,
userPrefs,
userPrefsStats,
stepUpPinSet,
stepUpUnlocked,
searchQuery: this._searchQuery,
searchScope: this._searchScope,
searchResults:
+1
View File
@@ -33,6 +33,7 @@
"pearcord-notifications": "git+https://git.ssh.surf/pearcord/pearcord-notifications.git#main",
"pearcord-settings": "git+https://git.ssh.surf/pearcord/pearcord-settings.git#main",
"pearcord-device-sync": "git+https://git.ssh.surf/pearcord/pearcord-device-sync.git#main",
"pearcord-step-up": "git+https://git.ssh.surf/pearcord/pearcord-step-up.git#main",
"hyperswarm": "^4.11.6",
"pearcord-shared": "git+https://git.ssh.surf/pearcord/pearcord-shared.git#main"
}