mirror of
https://github.com/docmost/docmost.git
synced 2026-08-19 21:51:36 +10:00
feat(integrations): add connectionScope (workspace|user) to OAuth flow
This commit is contained in:
@@ -149,15 +149,29 @@ export class OAuthService {
|
||||
? new Date(Date.now() + tokenResponse.expires_in * 1000)
|
||||
: null;
|
||||
|
||||
const connection = await this.connectionRepo.upsert({
|
||||
integrationId,
|
||||
userId,
|
||||
workspaceId,
|
||||
accessToken: encryptedAccessToken,
|
||||
refreshToken: encryptedRefreshToken,
|
||||
tokenExpiresAt,
|
||||
scopes: tokenResponse.scope ?? null,
|
||||
});
|
||||
const connectionScope =
|
||||
provider.definition.oauth?.connectionScope ?? 'user';
|
||||
|
||||
const connection =
|
||||
connectionScope === 'workspace'
|
||||
? await this.connectionRepo.upsertWorkspaceConnection({
|
||||
integrationId,
|
||||
userId,
|
||||
workspaceId,
|
||||
accessToken: encryptedAccessToken,
|
||||
refreshToken: encryptedRefreshToken,
|
||||
tokenExpiresAt,
|
||||
scopes: tokenResponse.scope ?? null,
|
||||
})
|
||||
: await this.connectionRepo.upsert({
|
||||
integrationId,
|
||||
userId,
|
||||
workspaceId,
|
||||
accessToken: encryptedAccessToken,
|
||||
refreshToken: encryptedRefreshToken,
|
||||
tokenExpiresAt,
|
||||
scopes: tokenResponse.scope ?? null,
|
||||
});
|
||||
|
||||
if (provider.onConnected) {
|
||||
await provider.onConnected({
|
||||
|
||||
@@ -4,6 +4,9 @@ export type OAuthConfig = {
|
||||
authUrl: string;
|
||||
tokenUrl: string;
|
||||
scopes: string[];
|
||||
// 'workspace' = one shared bot/app connection per integration (Slack model);
|
||||
// 'user' (default) = each Docmost user OAuths separately and gets their own token (Linear, Jira, GitHub model)
|
||||
connectionScope?: 'workspace' | 'user';
|
||||
};
|
||||
|
||||
export type UnfurlPattern = {
|
||||
|
||||
@@ -95,6 +95,60 @@ export class IntegrationConnectionRepo {
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async upsertWorkspaceConnection(
|
||||
input: {
|
||||
integrationId: string;
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
accessToken: string;
|
||||
refreshToken?: string | null;
|
||||
tokenExpiresAt?: Date | null;
|
||||
scopes?: string | null;
|
||||
},
|
||||
trx?: KyselyTransaction,
|
||||
): Promise<IntegrationConnection> {
|
||||
const db = dbOrTx(this.db, trx);
|
||||
|
||||
const existing = await this.findWorkspaceConnection(input.integrationId, trx);
|
||||
if (existing) {
|
||||
return this.update(
|
||||
existing.id,
|
||||
{
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken ?? null,
|
||||
tokenExpiresAt: input.tokenExpiresAt ?? null,
|
||||
scopes: input.scopes ?? null,
|
||||
userId: input.userId,
|
||||
},
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
// Clear any stale non-workspace row for the same (integration, user) to
|
||||
// avoid the uq(integration_id, user_id) constraint blocking the insert.
|
||||
await db
|
||||
.deleteFrom('integrationConnections')
|
||||
.where('integrationId', '=', input.integrationId)
|
||||
.where('userId', '=', input.userId)
|
||||
.where('kind', '!=', 'workspace')
|
||||
.execute();
|
||||
|
||||
return db
|
||||
.insertInto('integrationConnections')
|
||||
.values({
|
||||
integrationId: input.integrationId,
|
||||
userId: input.userId,
|
||||
workspaceId: input.workspaceId,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken ?? null,
|
||||
tokenExpiresAt: input.tokenExpiresAt ?? null,
|
||||
scopes: input.scopes ?? null,
|
||||
kind: 'workspace',
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async update(
|
||||
connectionId: string,
|
||||
data: UpdatableIntegrationConnection,
|
||||
|
||||
Reference in New Issue
Block a user