From 0cf1247f36a6e9680ecb2ff55314a5ec953f85f3 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 21 Feb 2026 03:44:20 +0000 Subject: [PATCH] fixes --- apps/server/src/common/helpers/utils.ts | 2 +- .../migrations/20250708T092214-scim.ts | 51 ----------- .../migrations/20260221T092214-scim.ts | 85 +++++++++++++++++++ .../src/database/repos/group/group.repo.ts | 38 +++++++-- .../src/database/repos/user/user.repo.ts | 4 + apps/server/src/database/types/db.d.ts | 18 ++++ .../server/src/database/types/entity.types.ts | 6 ++ apps/server/src/ee | 2 +- 8 files changed, 145 insertions(+), 61 deletions(-) delete mode 100644 apps/server/src/database/migrations/20250708T092214-scim.ts create mode 100644 apps/server/src/database/migrations/20260221T092214-scim.ts diff --git a/apps/server/src/common/helpers/utils.ts b/apps/server/src/common/helpers/utils.ts index 7c94bb48..6b9657b9 100644 --- a/apps/server/src/common/helpers/utils.ts +++ b/apps/server/src/common/helpers/utils.ts @@ -88,7 +88,7 @@ export function extractBearerTokenFromHeader( request: FastifyRequest, ): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; - return type === 'Bearer' ? token : undefined; + return type?.toLowerCase() === 'bearer' ? token : undefined; } export function hasLicenseOrEE(opts: { diff --git a/apps/server/src/database/migrations/20250708T092214-scim.ts b/apps/server/src/database/migrations/20250708T092214-scim.ts deleted file mode 100644 index 3f32abc7..00000000 --- a/apps/server/src/database/migrations/20250708T092214-scim.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { type Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await db.schema - .createTable('scim_tokens') - .addColumn('id', 'uuid', (col) => - col.primaryKey().defaultTo(sql`gen_uuid_v7()`), - ) - .addColumn('name', 'varchar', (col) => col.notNull()) - - .addColumn('token', 'varchar', (col) => col.notNull()) - - .addColumn('expires_at', 'timestamptz', (col) => col) - - .addColumn('last_used_at', 'timestamptz', (col) => col) - .addColumn('allow_signup', 'boolean', (col) => - col.defaultTo(false).notNull(), - ) - .addColumn('is_enabled', 'boolean', (col) => col.defaultTo(false).notNull()) - - .addColumn('creator_id', 'uuid', (col) => - col.references('users.id').onDelete('set null'), - ) - .addColumn('workspace_id', 'uuid', (col) => - col.references('workspaces.id').onDelete('cascade').notNull(), - ) - .addColumn('created_at', 'timestamptz', (col) => - col.notNull().defaultTo(sql`now()`), - ) - .addColumn('updated_at', 'timestamptz', (col) => - col.notNull().defaultTo(sql`now()`), - ) - .addColumn('deleted_at', 'timestamptz', (col) => col) - .execute(); - - await db.schema - .alterTable('workspaces') - .addColumn('is_scim_enabled', 'boolean', (col) => - col.defaultTo(false).notNull(), - ) - .execute(); -} - -export async function down(db: Kysely): Promise { - await db.schema.dropTable('scim_tokens').execute(); - - await db.schema - .alterTable('workspaces') - .dropColumn('is_scim_enabled') - .execute(); -} diff --git a/apps/server/src/database/migrations/20260221T092214-scim.ts b/apps/server/src/database/migrations/20260221T092214-scim.ts new file mode 100644 index 00000000..c53f1632 --- /dev/null +++ b/apps/server/src/database/migrations/20260221T092214-scim.ts @@ -0,0 +1,85 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('scim_tokens') + .addColumn('id', 'uuid', (col) => + col.primaryKey().defaultTo(sql`gen_uuid_v7()`), + ) + .addColumn('name', 'varchar', (col) => col.notNull()) + .addColumn('token_hash', 'varchar', (col) => col.notNull()) + .addColumn('token_last_four', 'varchar(4)', (col) => col.notNull()) + .addColumn('expires_at', 'timestamptz') + .addColumn('last_used_at', 'timestamptz') + .addColumn('is_enabled', 'boolean', (col) => + col.notNull().defaultTo(true), + ) + .addColumn('creator_id', 'uuid', (col) => + col.references('users.id').onDelete('set null'), + ) + .addColumn('workspace_id', 'uuid', (col) => + col.references('workspaces.id').onDelete('cascade').notNull(), + ) + .addColumn('created_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('updated_at', 'timestamptz', (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addColumn('deleted_at', 'timestamptz') + .execute(); + + await db.schema + .createIndex('idx_scim_tokens_token_hash') + .on('scim_tokens') + .column('token_hash') + .execute(); + + await db.schema + .createIndex('idx_scim_tokens_workspace_id') + .on('scim_tokens') + .column('workspace_id') + .execute(); + + await db.schema + .alterTable('users') + .addColumn('scim_external_id', 'text') + .execute(); + + await db.schema + .createIndex('idx_users_workspace_scim_external_id') + .on('users') + .columns(['workspace_id', 'scim_external_id']) + .where('scim_external_id', 'is not', null) + .unique() + .execute(); + + await db.schema + .alterTable('groups') + .addColumn('scim_external_id', 'text') + .execute(); + + await db.schema + .createIndex('idx_groups_workspace_scim_external_id') + .on('groups') + .columns(['workspace_id', 'scim_external_id']) + .where('scim_external_id', 'is not', null) + .unique() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('scim_tokens').execute(); + + await db.schema.dropIndex('idx_users_workspace_scim_external_id').execute(); + await db.schema + .alterTable('users') + .dropColumn('scim_external_id') + .execute(); + + await db.schema.dropIndex('idx_groups_workspace_scim_external_id').execute(); + await db.schema + .alterTable('groups') + .dropColumn('scim_external_id') + .execute(); +} diff --git a/apps/server/src/database/repos/group/group.repo.ts b/apps/server/src/database/repos/group/group.repo.ts index 558db6f7..e8a4d363 100644 --- a/apps/server/src/database/repos/group/group.repo.ts +++ b/apps/server/src/database/repos/group/group.repo.ts @@ -9,7 +9,7 @@ import { } from '@docmost/db/types/entity.types'; import { ExpressionBuilder, sql } from 'kysely'; import { PaginationOptions } from '../../pagination/pagination-options'; -import { DB } from '@docmost/db/types/db'; +import { DB, Groups } from '@docmost/db/types/db'; import { DefaultGroup } from '../../../core/group/dto/create-group.dto'; import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination'; @@ -17,16 +17,33 @@ import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagin export class GroupRepo { constructor(@InjectKysely() private readonly db: KyselyDB) {} + private baseFields: Array = [ + 'id', + 'name', + 'description', + 'isDefault', + 'creatorId', + 'workspaceId', + 'createdAt', + 'updatedAt', + 'deletedAt', + ]; + async findById( groupId: string, workspaceId: string, - opts?: { includeMemberCount?: boolean; trx?: KyselyTransaction }, + opts?: { + includeMemberCount?: boolean; + includeScimExternalId?: boolean; + trx?: KyselyTransaction; + }, ): Promise { const db = dbOrTx(this.db, opts?.trx); return db .selectFrom('groups') - .selectAll('groups') + .select(this.baseFields) .$if(opts?.includeMemberCount, (qb) => qb.select(this.withMemberCount)) + .$if(opts?.includeScimExternalId, (qb) => qb.select('scimExternalId')) .where('id', '=', groupId) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); @@ -35,13 +52,18 @@ export class GroupRepo { async findByName( groupName: string, workspaceId: string, - opts?: { includeMemberCount?: boolean; trx?: KyselyTransaction }, + opts?: { + includeMemberCount?: boolean; + includeScimExternalId?: boolean; + trx?: KyselyTransaction; + }, ): Promise { const db = dbOrTx(this.db, opts?.trx); return db .selectFrom('groups') - .selectAll('groups') + .select(this.baseFields) .$if(opts?.includeMemberCount, (qb) => qb.select(this.withMemberCount)) + .$if(opts?.includeScimExternalId, (qb) => qb.select('scimExternalId')) .where(sql`LOWER(name)`, '=', sql`LOWER(${groupName})`) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); @@ -71,7 +93,7 @@ export class GroupRepo { return db .insertInto('groups') .values(insertableGroup) - .returningAll() + .returning(this.baseFields) .executeTakeFirst(); } @@ -83,7 +105,7 @@ export class GroupRepo { return ( db .selectFrom('groups') - .selectAll() + .select(this.baseFields) // .select((eb) => this.withMemberCount(eb)) .where('isDefault', '=', true) .where('workspaceId', '=', workspaceId) @@ -109,7 +131,7 @@ export class GroupRepo { async getGroupsPaginated(workspaceId: string, pagination: PaginationOptions) { let baseQuery = this.db .selectFrom('groups') - .selectAll('groups') + .select(this.baseFields) .select((eb) => this.withMemberCount(eb)) .where('workspaceId', '=', workspaceId); diff --git a/apps/server/src/database/repos/user/user.repo.ts b/apps/server/src/database/repos/user/user.repo.ts index 3545e7ec..ce98c95e 100644 --- a/apps/server/src/database/repos/user/user.repo.ts +++ b/apps/server/src/database/repos/user/user.repo.ts @@ -43,6 +43,7 @@ export class UserRepo { opts?: { includePassword?: boolean; includeUserMfa?: boolean; + includeScimExternalId?: boolean; trx?: KyselyTransaction; }, ): Promise { @@ -52,6 +53,7 @@ export class UserRepo { .select(this.baseFields) .$if(opts?.includePassword, (qb) => qb.select('password')) .$if(opts?.includeUserMfa, (qb) => qb.select(this.withUserMfa)) + .$if(opts?.includeScimExternalId, (qb) => qb.select('scimExternalId')) .where('id', '=', userId) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); @@ -63,6 +65,7 @@ export class UserRepo { opts?: { includePassword?: boolean; includeUserMfa?: boolean; + includeScimExternalId?: boolean; trx?: KyselyTransaction; }, ): Promise { @@ -72,6 +75,7 @@ export class UserRepo { .select(this.baseFields) .$if(opts?.includePassword, (qb) => qb.select('password')) .$if(opts?.includeUserMfa, (qb) => qb.select(this.withUserMfa)) + .$if(opts?.includeScimExternalId, (qb) => qb.select('scimExternalId')) .where(sql`LOWER(email)`, '=', sql`LOWER(${email})`) .where('workspaceId', '=', workspaceId) .executeTakeFirst(); diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index 6668398b..f0a2d9e3 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -185,6 +185,7 @@ export interface Groups { id: Generated; isDefault: boolean; name: string; + scimExternalId: string | null; updatedAt: Generated; workspaceId: string; } @@ -309,6 +310,7 @@ export interface Users { name: string | null; password: string | null; role: string | null; + scimExternalId: string | null; settings: Json | null; timezone: string | null; updatedAt: Generated; @@ -378,6 +380,21 @@ export interface Notifications { createdAt: Generated; } +export interface ScimTokens { + createdAt: Generated; + deletedAt: Timestamp | null; + expiresAt: Timestamp | null; + id: Generated; + isEnabled: Generated; + lastUsedAt: Timestamp | null; + name: string; + tokenHash: string; + tokenLastFour: string; + creatorId: string | null; + updatedAt: Generated; + workspaceId: string; +} + export interface Watchers { id: Generated; userId: string; @@ -404,6 +421,7 @@ export interface DB { notifications: Notifications; pageHistory: PageHistory; pages: Pages; + scimTokens: ScimTokens; shares: Shares; spaceMembers: SpaceMembers; spaces: Spaces; diff --git a/apps/server/src/database/types/entity.types.ts b/apps/server/src/database/types/entity.types.ts index 65e1024a..3c96e8bc 100644 --- a/apps/server/src/database/types/entity.types.ts +++ b/apps/server/src/database/types/entity.types.ts @@ -21,6 +21,7 @@ import { FileTasks, UserMfa as _UserMFA, ApiKeys, + ScimTokens, Watchers, } from './db'; import { PageEmbeddings } from '@docmost/db/types/embeddings.types'; @@ -129,6 +130,11 @@ export type ApiKey = Selectable; export type InsertableApiKey = Insertable; export type UpdatableApiKey = Updateable>; +// Scim Tokens +export type ScimToken = Selectable; +export type InsertableScimToken = Insertable; +export type UpdatableScimToken = Updateable>; + // Page Embedding export type PageEmbedding = Selectable; export type InsertablePageEmbedding = Insertable; diff --git a/apps/server/src/ee b/apps/server/src/ee index a3b48000..bcf4a696 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit a3b48000bd4b7e1b6b0ff54c5764794070c63b34 +Subproject commit bcf4a6967b8e718cdac6d05342b258f442f618fa