mirror of
https://github.com/kennethnym/freya
synced 2026-06-19 16:11:18 +01:00
fix: accept credentials in source config upsert (#117)
* fix: unified source config + credentials Accept optional credentials in PUT /api/sources/:sourceId so the dashboard can send config and credentials in a single request, eliminating the race condition between parallel config/credential updates that left sources uninitialized until server restart. The existing /credentials endpoint is preserved for independent credential updates. Co-authored-by: Ona <no-reply@ona.com> * refactor: rename upsertSourceConfig to saveSourceConfig Co-authored-by: Ona <no-reply@ona.com> --------- Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -171,13 +171,18 @@ export class UserSessionManager {
|
||||
* inserts a new row if one doesn't exist and fully replaces config
|
||||
* (no merge).
|
||||
*
|
||||
* When `credentials` is provided, they are encrypted and persisted
|
||||
* alongside the config in the same flow, avoiding the race condition
|
||||
* of separate config + credential requests.
|
||||
*
|
||||
* @throws {SourceNotFoundError} if the sourceId has no registered provider
|
||||
* @throws {InvalidSourceConfigError} if config fails schema validation
|
||||
* @throws {CredentialStorageUnavailableError} if credentials are provided but no encryptor is configured
|
||||
*/
|
||||
async upsertSourceConfig(
|
||||
async saveSourceConfig(
|
||||
userId: string,
|
||||
sourceId: string,
|
||||
data: { enabled: boolean; config?: unknown },
|
||||
data: { enabled: boolean; config?: unknown; credentials?: unknown },
|
||||
): Promise<void> {
|
||||
const provider = this.providers.get(sourceId)
|
||||
if (!provider) {
|
||||
@@ -191,6 +196,10 @@ export class UserSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (data.credentials !== undefined && !this.encryptor) {
|
||||
throw new CredentialStorageUnavailableError()
|
||||
}
|
||||
|
||||
const config = data.config ?? {}
|
||||
|
||||
// Fetch existing row before upsert to capture credentials for session refresh.
|
||||
@@ -202,14 +211,24 @@ export class UserSessionManager {
|
||||
config,
|
||||
})
|
||||
|
||||
// Persist credentials after the upsert so the row exists.
|
||||
if (data.credentials !== undefined && this.encryptor) {
|
||||
const encrypted = this.encryptor.encrypt(JSON.stringify(data.credentials))
|
||||
await sources(this.db, userId).updateCredentials(sourceId, encrypted)
|
||||
}
|
||||
|
||||
const session = this.sessions.get(userId)
|
||||
if (session) {
|
||||
if (!data.enabled) {
|
||||
session.removeSource(sourceId)
|
||||
} else {
|
||||
const credentials = existingRow?.credentials
|
||||
? this.decryptCredentials(existingRow.credentials)
|
||||
: null
|
||||
// Prefer the just-provided credentials over what was in the DB.
|
||||
let credentials: unknown = null
|
||||
if (data.credentials !== undefined) {
|
||||
credentials = data.credentials
|
||||
} else if (existingRow?.credentials) {
|
||||
credentials = this.decryptCredentials(existingRow.credentials)
|
||||
}
|
||||
const source = await provider.feedSourceForUser(userId, config, credentials)
|
||||
if (session.hasSource(sourceId)) {
|
||||
session.replaceSource(sourceId, source)
|
||||
|
||||
Reference in New Issue
Block a user