mirror of
https://github.com/docmost/docmost.git
synced 2025-11-23 20:41:09 +10:00
custom domain support (cloud)
This commit is contained in:
@ -82,6 +82,7 @@
|
||||
"sanitize-filename-ts": "^1.0.2",
|
||||
"socket.io": "^4.8.1",
|
||||
"stripe": "^17.5.0",
|
||||
"tld-extract": "^2.1.0",
|
||||
"tmp-promise": "^3.0.3",
|
||||
"ws": "^8.18.2",
|
||||
"yauzl": "^3.2.0"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Injectable, NestMiddleware, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { EnvironmentService } from '../../integrations/environment/environment.service';
|
||||
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
||||
@ -27,8 +27,19 @@ export class DomainMiddleware implements NestMiddleware {
|
||||
(req as any).workspace = workspace;
|
||||
} else if (this.environmentService.isCloud()) {
|
||||
const header = req.headers.host;
|
||||
const subdomain = header.split('.')[0];
|
||||
|
||||
// First, try to find workspace by custom domain
|
||||
const workspaceByCustomDomain =
|
||||
await this.workspaceRepo.findByCustomDomain(header);
|
||||
|
||||
if (workspaceByCustomDomain) {
|
||||
(req as any).workspaceId = workspaceByCustomDomain.id;
|
||||
(req as any).workspace = workspaceByCustomDomain;
|
||||
return next();
|
||||
}
|
||||
|
||||
// Fall back to subdomain logic
|
||||
const subdomain = header.split('.')[0];
|
||||
const workspace = await this.workspaceRepo.findByHostname(subdomain);
|
||||
|
||||
if (!workspace) {
|
||||
|
||||
@ -134,7 +134,7 @@ export class AuthService {
|
||||
|
||||
const token = nanoIdGen(16);
|
||||
|
||||
const resetLink = `${this.domainService.getUrl(workspace.hostname)}/password-reset?token=${token}`;
|
||||
const resetLink = `${this.domainService.getUrl(workspace.hostname, workspace.customDomain)}/password-reset?token=${token}`;
|
||||
|
||||
await this.userTokenRepo.insertUserToken({
|
||||
token: token,
|
||||
|
||||
@ -171,7 +171,7 @@ export class WorkspaceInvitationService {
|
||||
invitation.email,
|
||||
invitation.token,
|
||||
authUser.name,
|
||||
workspace.hostname,
|
||||
workspace,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -317,7 +317,7 @@ export class WorkspaceInvitationService {
|
||||
invitation.email,
|
||||
invitation.token,
|
||||
invitedByUser.name,
|
||||
workspace.hostname,
|
||||
workspace,
|
||||
);
|
||||
}
|
||||
|
||||
@ -340,17 +340,17 @@ export class WorkspaceInvitationService {
|
||||
return this.buildInviteLink({
|
||||
invitationId,
|
||||
inviteToken: token.token,
|
||||
hostname: workspace.hostname,
|
||||
workspace: workspace,
|
||||
});
|
||||
}
|
||||
|
||||
async buildInviteLink(opts: {
|
||||
invitationId: string;
|
||||
inviteToken: string;
|
||||
hostname?: string;
|
||||
workspace: Workspace;
|
||||
}): Promise<string> {
|
||||
const { invitationId, inviteToken, hostname } = opts;
|
||||
return `${this.domainService.getUrl(hostname)}/invites/${invitationId}?token=${inviteToken}`;
|
||||
const { invitationId, inviteToken, workspace } = opts;
|
||||
return `${this.domainService.getUrl(workspace.hostname, workspace.customDomain)}/invites/${invitationId}?token=${inviteToken}`;
|
||||
}
|
||||
|
||||
async sendInvitationMail(
|
||||
@ -358,12 +358,12 @@ export class WorkspaceInvitationService {
|
||||
inviteeEmail: string,
|
||||
inviteToken: string,
|
||||
invitedByName: string,
|
||||
hostname?: string,
|
||||
workspace: Workspace,
|
||||
): Promise<void> {
|
||||
const inviteLink = await this.buildInviteLink({
|
||||
invitationId,
|
||||
inviteToken,
|
||||
hostname,
|
||||
workspace,
|
||||
});
|
||||
|
||||
const emailTemplate = InvitationEmail({
|
||||
|
||||
@ -83,6 +83,14 @@ export class WorkspaceRepo {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async findByCustomDomain(domain: string): Promise<Workspace> {
|
||||
return await this.db
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where(sql`LOWER(custom_domain)`, '=', sql`LOWER(${domain})`)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async hostnameExists(
|
||||
hostname: string,
|
||||
trx?: KyselyTransaction,
|
||||
|
||||
Submodule apps/server/src/ee updated: 19197d2610...1e127cec1d
@ -5,10 +5,13 @@ import { EnvironmentService } from './environment.service';
|
||||
export class DomainService {
|
||||
constructor(private environmentService: EnvironmentService) {}
|
||||
|
||||
getUrl(hostname?: string): string {
|
||||
getUrl(hostname?: string, customDomain?: string): string {
|
||||
if (!this.environmentService.isCloud()) {
|
||||
return this.environmentService.getAppUrl();
|
||||
}
|
||||
if (customDomain) {
|
||||
return customDomain;
|
||||
}
|
||||
|
||||
const domain = this.environmentService.getSubdomainHost();
|
||||
if (!hostname || !domain) {
|
||||
|
||||
@ -68,6 +68,10 @@ export class EnvironmentVariables {
|
||||
)
|
||||
@ValidateIf((obj) => obj.CLOUD === 'true'.toLowerCase())
|
||||
SUBDOMAIN_HOST: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((obj) => obj.CLOUD === 'true'.toLowerCase())
|
||||
APP_IP: string;
|
||||
}
|
||||
|
||||
export function validate(config: Record<string, any>) {
|
||||
|
||||
Reference in New Issue
Block a user