This commit is contained in:
Philipinho
2026-02-21 03:44:20 +00:00
parent 77ce9b5cd4
commit 0cf1247f36
8 changed files with 145 additions and 61 deletions
+1 -1
View File
@@ -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: {
@@ -1,51 +0,0 @@
import { type Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
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<any>): Promise<void> {
await db.schema.dropTable('scim_tokens').execute();
await db.schema
.alterTable('workspaces')
.dropColumn('is_scim_enabled')
.execute();
}
@@ -0,0 +1,85 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
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<any>): Promise<void> {
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();
}
@@ -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<keyof Groups> = [
'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<Group> {
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<Group> {
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);
@@ -43,6 +43,7 @@ export class UserRepo {
opts?: {
includePassword?: boolean;
includeUserMfa?: boolean;
includeScimExternalId?: boolean;
trx?: KyselyTransaction;
},
): Promise<User> {
@@ -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<User> {
@@ -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();
+18
View File
@@ -185,6 +185,7 @@ export interface Groups {
id: Generated<string>;
isDefault: boolean;
name: string;
scimExternalId: string | null;
updatedAt: Generated<Timestamp>;
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<Timestamp>;
@@ -378,6 +380,21 @@ export interface Notifications {
createdAt: Generated<Timestamp>;
}
export interface ScimTokens {
createdAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
expiresAt: Timestamp | null;
id: Generated<string>;
isEnabled: Generated<boolean>;
lastUsedAt: Timestamp | null;
name: string;
tokenHash: string;
tokenLastFour: string;
creatorId: string | null;
updatedAt: Generated<Timestamp>;
workspaceId: string;
}
export interface Watchers {
id: Generated<string>;
userId: string;
@@ -404,6 +421,7 @@ export interface DB {
notifications: Notifications;
pageHistory: PageHistory;
pages: Pages;
scimTokens: ScimTokens;
shares: Shares;
spaceMembers: SpaceMembers;
spaces: Spaces;
@@ -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<ApiKeys>;
export type InsertableApiKey = Insertable<ApiKeys>;
export type UpdatableApiKey = Updateable<Omit<ApiKeys, 'id'>>;
// Scim Tokens
export type ScimToken = Selectable<ScimTokens>;
export type InsertableScimToken = Insertable<ScimTokens>;
export type UpdatableScimToken = Updateable<Omit<ScimTokens, 'id'>>;
// Page Embedding
export type PageEmbedding = Selectable<PageEmbeddings>;
export type InsertablePageEmbedding = Insertable<PageEmbeddings>;