Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
import type { ConnectorConnection } from "@tactical-media-broadcast-hub/core";
|
|
|
|
/**
|
|
* Opaque credential access — connectors never handle ciphertext or raw vault
|
|
* material. The vault (WP2) implements this port; the in-memory version here
|
|
* serves development and contract tests.
|
|
*/
|
|
export interface CredentialAccessor {
|
|
getAccessToken(connection: ConnectorConnection): Promise<string>;
|
|
getSecret(connection: ConnectorConnection, key: string): Promise<string>;
|
|
put(connection: ConnectorConnection, key: string, value: string): Promise<void>;
|
|
}
|
|
|
|
export class InMemoryCredentials implements CredentialAccessor {
|
|
private readonly store = new Map<string, string>();
|
|
|
|
private key(connection: ConnectorConnection, name: string): string {
|
|
return `${connection.id}:${name}`;
|
|
}
|
|
|
|
async getAccessToken(connection: ConnectorConnection): Promise<string> {
|
|
const token = this.store.get(this.key(connection, "access_token"));
|
|
if (token === undefined) throw new Error(`no access token for connection ${connection.id}`);
|
|
return token;
|
|
}
|
|
|
|
async getSecret(connection: ConnectorConnection, key: string): Promise<string> {
|
|
const secret = this.store.get(this.key(connection, key));
|
|
if (secret === undefined) throw new Error(`no secret "${key}" for connection ${connection.id}`);
|
|
return secret;
|
|
}
|
|
|
|
async put(connection: ConnectorConnection, key: string, value: string): Promise<void> {
|
|
this.store.set(this.key(connection, key), value);
|
|
}
|
|
}
|