initial relay scaffold
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { configFile } from '../file-models/config.json'
|
||||
import { randomBytes, scryptSync } from 'crypto'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
const SCRYPT_KEYLEN = 64
|
||||
|
||||
// Mirror of Recap's setAdminPassword — same shape so server-side
|
||||
// admin-auth code can be lifted with minimal change.
|
||||
const inputSpec = InputSpec.of({
|
||||
relay_admin_username: Value.text({
|
||||
name: 'Admin Username',
|
||||
description: 'Username for the relay admin dashboard. Defaults to "admin".',
|
||||
required: true,
|
||||
default: 'admin',
|
||||
minLength: 1,
|
||||
maxLength: 64,
|
||||
}),
|
||||
relay_admin_password: Value.text({
|
||||
name: 'Admin Password',
|
||||
description:
|
||||
'Password for the relay admin dashboard. Must be at least 8 characters. Leave blank to disable /admin entirely (useful while testing /relay/* endpoints).',
|
||||
required: false,
|
||||
default: null,
|
||||
masked: true,
|
||||
minLength: 0,
|
||||
maxLength: 256,
|
||||
}),
|
||||
relay_admin_password_confirm: Value.text({
|
||||
name: 'Confirm Password',
|
||||
description: 'Re-enter the password to confirm.',
|
||||
required: false,
|
||||
default: null,
|
||||
masked: true,
|
||||
minLength: 0,
|
||||
maxLength: 256,
|
||||
}),
|
||||
})
|
||||
|
||||
export const setAdminPassword = sdk.Action.withInput(
|
||||
'set-admin-password',
|
||||
|
||||
async ({ effects }) => ({
|
||||
name: 'Set Admin Password',
|
||||
description:
|
||||
"Gate the relay's /admin dashboard. The public /relay/* endpoints are unaffected — they're per-call authenticated via X-Recap-Install-Id + Authorization headers.",
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
inputSpec,
|
||||
|
||||
async ({ effects }) => {
|
||||
const config = await configFile.read().once()
|
||||
return {
|
||||
relay_admin_username: config?.relay_admin_username || 'admin',
|
||||
relay_admin_password: undefined,
|
||||
relay_admin_password_confirm: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
async ({ effects, input }) => {
|
||||
const username = (input.relay_admin_username || '').trim()
|
||||
const password = input.relay_admin_password || ''
|
||||
const confirm = input.relay_admin_password_confirm || ''
|
||||
|
||||
if (!username) throw new Error('Username is required.')
|
||||
|
||||
if (password === '' && confirm === '') {
|
||||
await configFile.merge(effects, {
|
||||
relay_admin_username: username,
|
||||
relay_admin_password_hash: '',
|
||||
relay_admin_password_salt: '',
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
if (password !== confirm) {
|
||||
throw new Error('Password and confirmation do not match.')
|
||||
}
|
||||
if (password.length < 8) {
|
||||
throw new Error('Password must be at least 8 characters.')
|
||||
}
|
||||
|
||||
const salt = randomBytes(16).toString('hex')
|
||||
const hash = scryptSync(password, salt, SCRYPT_KEYLEN).toString('hex')
|
||||
|
||||
const existing = await configFile.read().once()
|
||||
const sessionSecret =
|
||||
existing?.relay_admin_session_secret &&
|
||||
existing.relay_admin_session_secret.length > 0
|
||||
? existing.relay_admin_session_secret
|
||||
: randomBytes(32).toString('hex')
|
||||
|
||||
await configFile.merge(effects, {
|
||||
relay_admin_username: username,
|
||||
relay_admin_password_hash: hash,
|
||||
relay_admin_password_salt: salt,
|
||||
relay_admin_session_secret: sessionSecret,
|
||||
})
|
||||
|
||||
return null
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user