mirror of
https://github.com/certd/certd.git
synced 2026-04-23 11:37:23 +08:00
pref: 支持子域名托管的域名证书申请
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
|
||||
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
|
||||
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
|
||||
import { CnameProvider, CnameRecord } from '@certd/pipeline';
|
||||
import { cache, http, logger, utils } from '@certd/basic';
|
||||
|
||||
import { AccessService } from '@certd/lib-server';
|
||||
import { isDev } from '@certd/basic';
|
||||
import { walkTxtRecord } from '@certd/acme-client';
|
||||
import { CnameProviderService } from './cname-provider-service.js';
|
||||
import { CnameProviderEntity } from '../entity/cname-provider.js';
|
||||
import { CommonDnsProvider } from './common-provider.js';
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {AccessService, BaseService, PlusService, ValidateException} from '@certd/lib-server';
|
||||
import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.js';
|
||||
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
|
||||
import {CnameProvider, CnameRecord} from '@certd/pipeline';
|
||||
import {cache, http, isDev, logger, utils} from '@certd/basic';
|
||||
import {walkTxtRecord} from '@certd/acme-client';
|
||||
import {CnameProviderService} from './cname-provider-service.js';
|
||||
import {CnameProviderEntity} from '../entity/cname-provider.js';
|
||||
import {CommonDnsProvider} from './common-provider.js';
|
||||
import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
|
||||
|
||||
type CnameCheckCacheValue = {
|
||||
validating: boolean;
|
||||
@@ -40,6 +39,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@Inject()
|
||||
plusService: PlusService;
|
||||
|
||||
@Inject()
|
||||
subDomainService: SubDomainService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -74,17 +76,20 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
} else {
|
||||
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
}
|
||||
this.cnameProviderChanged(param, cnameProvider);
|
||||
await this.cnameProviderChanged(param.userId,param, cnameProvider);
|
||||
|
||||
param.status = 'cname';
|
||||
const { id } = await super.add(param);
|
||||
return await this.info(id);
|
||||
}
|
||||
|
||||
private cnameProviderChanged(param: any, cnameProvider: CnameProviderEntity) {
|
||||
private async cnameProviderChanged(userId:number,param: any, cnameProvider: CnameProviderEntity) {
|
||||
param.cnameProviderId = cnameProvider.id;
|
||||
|
||||
const realDomain = parseDomain(param.domain);
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const realDomain = await domainParser.parse(param.domain);
|
||||
const prefix = param.domain.replace(realDomain, '');
|
||||
let hostRecord = `_acme-challenge.${prefix}`;
|
||||
if (hostRecord.endsWith('.')) {
|
||||
@@ -111,7 +116,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
if (old.cnameProviderId !== param.cnameProviderId) {
|
||||
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
this.cnameProviderChanged(param, cnameProvider);
|
||||
await this.cnameProviderChanged(old.userId,param, cnameProvider);
|
||||
param.status = 'cname';
|
||||
}
|
||||
return await super.update(param);
|
||||
@@ -185,6 +190,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const cacheKey = `cname.record.verify.${bean.id}`;
|
||||
|
||||
let value: CnameCheckCacheValue = cache.get(cacheKey);
|
||||
@@ -219,7 +227,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||
const context = { access, logger, http, utils };
|
||||
const context = { access, logger, http, utils,domainParser };
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
context,
|
||||
@@ -239,7 +247,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return false;
|
||||
}
|
||||
|
||||
const originDomain = parseDomain(bean.domain);
|
||||
|
||||
const originDomain = await domainParser.parse(bean.domain);
|
||||
const fullDomain = `${bean.hostRecord}.${originDomain}`;
|
||||
|
||||
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||
@@ -285,7 +294,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
ttl: ttl,
|
||||
});
|
||||
|
||||
const domain = parseDomain(bean.recordValue);
|
||||
const domain = await domainParser.parse(bean.recordValue);
|
||||
const fullRecord = bean.recordValue;
|
||||
const hostRecord = fullRecord.replace(`.${domain}`, '');
|
||||
const req = {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 子域名托管
|
||||
*/
|
||||
@Entity('pi_sub_domain')
|
||||
export class SubDomainEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: 'UserId' })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'domain', comment: '子域名' })
|
||||
domain: string;
|
||||
|
||||
@Column({ name: 'disabled', comment: '禁用' })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
comment: '修改时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, MoreThan, Repository } from "typeorm";
|
||||
import {Config, Inject, Provide, Scope, ScopeEnum, sleep} from "@midwayjs/core";
|
||||
import {InjectEntityModel} from "@midwayjs/typeorm";
|
||||
import {In, MoreThan, Repository} from "typeorm";
|
||||
import {
|
||||
AccessGetter,
|
||||
AccessService,
|
||||
BaseService,
|
||||
NeedSuiteException,
|
||||
@@ -12,30 +11,40 @@ import {
|
||||
SysSettingsService,
|
||||
SysSiteInfo
|
||||
} from "@certd/lib-server";
|
||||
import { PipelineEntity } from "../entity/pipeline.js";
|
||||
import { PipelineDetail } from "../entity/vo/pipeline-detail.js";
|
||||
import { Executor, Pipeline, ResultType, RunHistory, RunnableCollection, SysInfo, UserInfo } from "@certd/pipeline";
|
||||
import { DbStorage } from "./db-storage.js";
|
||||
import { StorageService } from "./storage-service.js";
|
||||
import { Cron } from "../../cron/cron.js";
|
||||
import { HistoryService } from "./history-service.js";
|
||||
import { HistoryEntity } from "../entity/history.js";
|
||||
import { HistoryLogEntity } from "../entity/history-log.js";
|
||||
import { HistoryLogService } from "./history-log-service.js";
|
||||
import { EmailService } from "../../basic/service/email-service.js";
|
||||
import { UserService } from "../../sys/authority/service/user-service.js";
|
||||
import { CnameRecordService } from "../../cname/service/cname-record-service.js";
|
||||
import { CnameProxyService } from "./cname-proxy-service.js";
|
||||
import { PluginConfigGetter } from "../../plugin/service/plugin-config-getter.js";
|
||||
import {PipelineEntity} from "../entity/pipeline.js";
|
||||
import {PipelineDetail} from "../entity/vo/pipeline-detail.js";
|
||||
import {
|
||||
Executor,
|
||||
IAccessService,
|
||||
ICnameProxyService,
|
||||
INotificationService,
|
||||
Pipeline,
|
||||
ResultType,
|
||||
RunHistory,
|
||||
RunnableCollection,
|
||||
SysInfo,
|
||||
UserInfo
|
||||
} from "@certd/pipeline";
|
||||
import {DbStorage} from "./db-storage.js";
|
||||
import {StorageService} from "./storage-service.js";
|
||||
import {Cron} from "../../cron/cron.js";
|
||||
import {HistoryService} from "./history-service.js";
|
||||
import {HistoryEntity} from "../entity/history.js";
|
||||
import {HistoryLogEntity} from "../entity/history-log.js";
|
||||
import {HistoryLogService} from "./history-log-service.js";
|
||||
import {EmailService} from "../../basic/service/email-service.js";
|
||||
import {UserService} from "../../sys/authority/service/user-service.js";
|
||||
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
|
||||
import {PluginConfigGetter} from "../../plugin/service/plugin-config-getter.js";
|
||||
import dayjs from "dayjs";
|
||||
import { DbAdapter } from "../../db/index.js";
|
||||
import { isComm } from "@certd/plus-core";
|
||||
import { logger } from "@certd/basic";
|
||||
import { UrlService } from "./url-service.js";
|
||||
import { NotificationService } from "./notification-service.js";
|
||||
import { NotificationGetter } from "./notification-getter.js";
|
||||
import { UserSuiteEntity, UserSuiteService } from "@certd/commercial-core";
|
||||
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
|
||||
import {DbAdapter} from "../../db/index.js";
|
||||
import {isComm} from "@certd/plus-core";
|
||||
import {logger} from "@certd/basic";
|
||||
import {UrlService} from "./url-service.js";
|
||||
import {NotificationService} from "./notification-service.js";
|
||||
import {UserSuiteEntity, UserSuiteService} from "@certd/commercial-core";
|
||||
import {CertInfoService} from "../../monitor/service/cert-info-service.js";
|
||||
import {TaskServiceBuilder} from "./task-service-getter.js";
|
||||
|
||||
const runningTasks: Map<string | number, Executor> = new Map();
|
||||
|
||||
@@ -65,6 +74,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
@Inject()
|
||||
pluginConfigGetter: PluginConfigGetter;
|
||||
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@@ -473,20 +485,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
role: userIsAdmin ? 'admin' : 'user',
|
||||
};
|
||||
|
||||
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
|
||||
const cnameProxyService = new CnameProxyService(userId, this.cnameRecordService.getWithAccessByDomain.bind(this.cnameRecordService));
|
||||
const notificationGetter = new NotificationGetter(userId, this.notificationService);
|
||||
|
||||
const sysInfo: SysInfo = {};
|
||||
if (isComm()) {
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
sysInfo.title = siteInfo.title;
|
||||
}
|
||||
const serviceContainer = {}
|
||||
const serviceGetter = {
|
||||
get:(name: string) => {
|
||||
return serviceContainer[name]
|
||||
}
|
||||
}
|
||||
|
||||
const taskServiceGetter = this.taskServiceBuilder.create({
|
||||
userId,
|
||||
})
|
||||
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService")
|
||||
const notificationGetter =await taskServiceGetter.get<INotificationService>("notificationService")
|
||||
const cnameProxyService =await taskServiceGetter.get<ICnameProxyService>("cnameProxyService")
|
||||
const executor = new Executor({
|
||||
user,
|
||||
pipeline,
|
||||
@@ -500,7 +511,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
notificationService: notificationGetter,
|
||||
fileRootDir: this.certdConfig.fileRootDir,
|
||||
sysInfo,
|
||||
serviceGetter
|
||||
serviceGetter:taskServiceGetter
|
||||
});
|
||||
try {
|
||||
runningTasks.set(historyId, executor);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {BaseService, SysSettingsService} from '@certd/lib-server';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {SubDomainEntity} from '../entity/sub-domain.js';
|
||||
import {EmailService} from '../../basic/service/email-service.js';
|
||||
import {ISubDomainsGetter} from "@certd/plugin-cert";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class SubDomainService extends BaseService<SubDomainEntity> {
|
||||
@InjectEntityModel(SubDomainEntity)
|
||||
repository: Repository<SubDomainEntity>;
|
||||
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async getListByUserId(userId:number):Promise<string[]>{
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
const list = await this.find({
|
||||
where: {
|
||||
userId,
|
||||
disabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
return list.map(item=>item.domain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export class SubDomainsGetter implements ISubDomainsGetter {
|
||||
userId: number;
|
||||
subDomainService: SubDomainService;
|
||||
|
||||
constructor(userId: number, subDomainService: SubDomainService) {
|
||||
this.userId = userId;
|
||||
this.subDomainService = subDomainService;
|
||||
}
|
||||
|
||||
async getSubDomains() {
|
||||
return await this.subDomainService.getListByUserId(this.userId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {IServiceGetter} from "@certd/pipeline";
|
||||
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
|
||||
import {SubDomainService, SubDomainsGetter} from "./sub-domain-service.js";
|
||||
import {AccessGetter, AccessService} from "@certd/lib-server";
|
||||
import {CnameProxyService} from "./cname-proxy-service.js";
|
||||
import {NotificationGetter} from "./notification-getter.js";
|
||||
import {NotificationService} from "./notification-service.js";
|
||||
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
|
||||
|
||||
export class TaskServiceGetter implements IServiceGetter{
|
||||
serviceContainer:Record<string, any>;
|
||||
constructor(serviceContainer:Record<string, any>) {
|
||||
this.serviceContainer = serviceContainer;
|
||||
}
|
||||
async get<T>(serviceName: string): Promise<T> {
|
||||
const ret = this.serviceContainer[serviceName] as T;
|
||||
if(!ret){
|
||||
throw new Error(`service ${serviceName} not found`)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export type TaskServiceContainer = {
|
||||
subDomainsGetter:SubDomainsGetter;
|
||||
accessService: AccessGetter;
|
||||
cnameProxyService: CnameProxyService;
|
||||
notificationService: NotificationGetter;
|
||||
}
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TaskServiceBuilder {
|
||||
|
||||
@Inject()
|
||||
subDomainService: SubDomainService;
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
@Inject()
|
||||
cnameRecordService: CnameRecordService;
|
||||
@Inject()
|
||||
notificationService: NotificationService;
|
||||
|
||||
|
||||
create(req:TaskServiceCreateReq){
|
||||
|
||||
const userId = req.userId;
|
||||
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
|
||||
const cnameProxyService = new CnameProxyService(userId, this.cnameRecordService.getWithAccessByDomain.bind(this.cnameRecordService));
|
||||
const notificationGetter = new NotificationGetter(userId, this.notificationService);
|
||||
|
||||
const serviceContainer:TaskServiceContainer = {
|
||||
subDomainsGetter:new SubDomainsGetter(req.userId, this.subDomainService),
|
||||
accessService: accessGetter,
|
||||
cnameProxyService:cnameProxyService,
|
||||
notificationService:notificationGetter
|
||||
}
|
||||
return new TaskServiceGetter(serviceContainer)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user