mirror of
https://github.com/hanzoai/social.git
synced 2026-10-27 09:19:48 +00:00
* Say when a connector catalog is loading, missing, or unconfigured. The Connect card treated a /v1/provider read that had not answered as "not in the catalog". Cards now say whether that read is in flight, failed, the network is absent, or present without app credentials — and name the connect-plane variables, keeping the publish plane's names when they differ. The read asks for each social network by id instead of the unfiltered registry. Co-authored-by: z <[email protected]> * Let an organization keep several accounts on one network. Connect already listed every handle the social API returned, but it never said which was a person and which was the company. Each account can now be personal or the network's own business kind — company on X, a Company Page on LinkedIn, a Page on Facebook, Business on Instagram — and the composer counts those kinds. The kind is stored on /v1/settings/social-audiences so it does not share a document with the Postiz post settings. Co-authored-by: z <[email protected]> * Add a Keys page for an organization's own app credentials. GET and PUT /v1/social/credentials are the contract the connect plane is growing: the public client id comes back, the secret never does, an omitted secret is left sealed, and an empty secret clears it. The screen masks both. A deployment that answers 404 or 501 has not shipped the route, and Keys stays off the rail until it has. Co-authored-by: z <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: z <[email protected]>
78 lines
2.9 KiB
JavaScript
78 lines
2.9 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
|
|
import {
|
|
credentialWrite,
|
|
credentialsLive,
|
|
credentialsUnshipped,
|
|
maskId,
|
|
maskSecret,
|
|
parseCredentials,
|
|
} from '../src/credentials.ts'
|
|
|
|
test('a credential catalog drops secrets and accepts either envelope', () => {
|
|
assert.deepEqual(
|
|
parseCredentials({
|
|
apps: [
|
|
{ provider: 'x', clientId: 'pub-1234', secretSet: true, clientSecret: 'nope' },
|
|
{ provider: ' ', clientId: 'skip' },
|
|
{ provider: 'facebook', secretSet: false },
|
|
],
|
|
}),
|
|
[
|
|
{ provider: 'x', clientId: 'pub-1234', secretSet: true },
|
|
{ provider: 'facebook', clientId: '', secretSet: false },
|
|
],
|
|
)
|
|
assert.deepEqual(parseCredentials({ data: [{ provider: 'tiktok', clientId: 'tt' }] }), [
|
|
{ provider: 'tiktok', clientId: 'tt', secretSet: false },
|
|
])
|
|
assert.deepEqual(parseCredentials(null), [])
|
|
assert.equal(JSON.stringify(parseCredentials({ apps: [{ provider: 'x', clientSecret: 'leak' }] })).includes('leak'), false)
|
|
})
|
|
|
|
test('a write sends a secret only when one was typed or cleared, and never a mask', () => {
|
|
const base = { provider: 'x', clientId: 'pub-1234', nextClientId: '', nextSecret: '', clearSecret: false }
|
|
assert.equal(credentialWrite(base), null)
|
|
assert.deepEqual(credentialWrite({ ...base, nextSecret: ' s3cret ' }), {
|
|
provider: 'x',
|
|
clientId: 'pub-1234',
|
|
clientSecret: 's3cret',
|
|
})
|
|
assert.deepEqual(credentialWrite({ ...base, clearSecret: true }), {
|
|
provider: 'x',
|
|
clientId: 'pub-1234',
|
|
clientSecret: '',
|
|
})
|
|
assert.deepEqual(credentialWrite({ ...base, nextClientId: 'pub-9999' }), {
|
|
provider: 'x',
|
|
clientId: 'pub-9999',
|
|
})
|
|
const replaced = credentialWrite({ ...base, nextClientId: 'pub-9999', nextSecret: 'new' })
|
|
assert.equal(replaced.clientSecret, 'new')
|
|
assert.equal(Object.hasOwn(replaced, 'clientSecret'), true)
|
|
assert.equal(JSON.stringify(replaced).includes('••••'), false)
|
|
})
|
|
|
|
test('ids and secrets are masked, and a short id is hidden whole', () => {
|
|
assert.equal(maskId(''), '')
|
|
assert.equal(maskId('ab'), '••••')
|
|
assert.equal(maskId('pub-1234'), '••••1234')
|
|
assert.equal(maskSecret(false), '')
|
|
assert.equal(maskSecret(true), '••••••••')
|
|
assert.equal(maskSecret(true).includes('value'), false)
|
|
})
|
|
|
|
test('Keys stays off the rail until the route is known to exist', () => {
|
|
assert.equal(credentialsUnshipped({ status: 404 }), true)
|
|
assert.equal(credentialsUnshipped({ status: 501 }), true)
|
|
assert.equal(credentialsUnshipped({ status: 403 }), false)
|
|
assert.equal(credentialsUnshipped({ status: 500 }), false)
|
|
assert.equal(credentialsUnshipped(new Error('down')), false)
|
|
|
|
assert.equal(credentialsLive(null, false), false)
|
|
assert.equal(credentialsLive({ unshipped: true }, false), false)
|
|
assert.equal(credentialsLive({ unshipped: false }, false), true)
|
|
assert.equal(credentialsLive(null, true), true)
|
|
})
|