Files
social/server/credentials.test.mjs
b272d4701c Tell the truth on Connect, and let an org keep its own accounts and keys (#9)
* 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]>
2026-10-26 17:56:06 -07:00

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)
})