Add generate key command (#18171)
Will be useful to auto log users on app creation
This commit is contained in:
@@ -8,6 +8,7 @@ import { ConfirmationQuestion } from 'src/database/commands/questions/confirmati
|
||||
import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
||||
import { EventLogCleanupModule } from 'src/engine/core-modules/event-logs/cleanup/event-log-cleanup.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
@@ -60,6 +61,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
|
||||
ConfirmationQuestion,
|
||||
CronRegisterAllCommand,
|
||||
ListOrphanedWorkspaceEntitiesCommand,
|
||||
GenerateApiKeyCommand,
|
||||
],
|
||||
})
|
||||
export class DatabaseCommandModule {}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
|
||||
import { ApiKeyResolver } from 'src/engine/core-modules/api-key/api-key.resolver';
|
||||
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
import { ApiKeyService } from 'src/engine/core-modules/api-key/services/api-key.service';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
@@ -34,8 +35,18 @@ import { ApiKeyController } from './controllers/api-key.controller';
|
||||
TokenModule,
|
||||
PermissionsModule,
|
||||
],
|
||||
providers: [ApiKeyService, ApiKeyResolver, ApiKeyRoleService],
|
||||
providers: [
|
||||
ApiKeyService,
|
||||
ApiKeyResolver,
|
||||
ApiKeyRoleService,
|
||||
GenerateApiKeyCommand,
|
||||
],
|
||||
controllers: [ApiKeyController],
|
||||
exports: [ApiKeyService, ApiKeyRoleService, TypeOrmModule],
|
||||
exports: [
|
||||
ApiKeyService,
|
||||
ApiKeyRoleService,
|
||||
TypeOrmModule,
|
||||
GenerateApiKeyCommand,
|
||||
],
|
||||
})
|
||||
export class ApiKeyModule {}
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { addDays } from 'date-fns';
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||
|
||||
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
|
||||
import { ApiKeyService } from 'src/engine/core-modules/api-key/services/api-key.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { STANDARD_ROLE } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-role.constant';
|
||||
|
||||
type GenerateApiKeyCommandOptions = {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
expiresIn?: number;
|
||||
};
|
||||
|
||||
const NEVER_EXPIRE_DAYS = 100 * 365;
|
||||
|
||||
@Command({
|
||||
name: 'workspace:generate-api-key',
|
||||
description: 'Generate an API key for a workspace and output the token',
|
||||
})
|
||||
export class GenerateApiKeyCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(GenerateApiKeyCommand.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
private readonly apiKeyService: ApiKeyService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id <workspaceId>',
|
||||
description: 'Workspace ID (required)',
|
||||
required: true,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-n, --name <name>',
|
||||
description: 'Name of the API key',
|
||||
defaultValue: 'Developer API Key',
|
||||
})
|
||||
parseName(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-e, --expires-in <days>',
|
||||
description:
|
||||
'Number of days until expiration. Omit for a non-expiring key.',
|
||||
})
|
||||
parseExpiresIn(value: string): number {
|
||||
const days = parseInt(value, 10);
|
||||
|
||||
if (isNaN(days) || days <= 0) {
|
||||
throw new Error('--expires-in must be a positive number of days');
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParams: string[],
|
||||
options: GenerateApiKeyCommandOptions,
|
||||
): Promise<void> {
|
||||
const nodeEnv = this.twentyConfigService.get('NODE_ENV');
|
||||
|
||||
if (
|
||||
nodeEnv !== NodeEnvironment.DEVELOPMENT &&
|
||||
nodeEnv !== NodeEnvironment.TEST
|
||||
) {
|
||||
throw new Error(
|
||||
'This command is only available in development or test environments',
|
||||
);
|
||||
}
|
||||
|
||||
const expiresAt = addDays(
|
||||
new Date(),
|
||||
options.expiresIn ?? NEVER_EXPIRE_DAYS,
|
||||
);
|
||||
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: options.workspaceId },
|
||||
});
|
||||
|
||||
if (!isDefined(workspace)) {
|
||||
this.logger.error(`Workspace ${options.workspaceId} not found.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const adminRole = await this.roleRepository.findOne({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
universalIdentifier: STANDARD_ROLE.admin.universalIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(adminRole)) {
|
||||
this.logger.error(`No Admin role found for workspace ${workspace.id}.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let apiKey: ApiKeyEntity;
|
||||
|
||||
try {
|
||||
apiKey = await this.apiKeyService.create({
|
||||
name: options.name,
|
||||
expiresAt,
|
||||
workspaceId: workspace.id,
|
||||
roleId: adminRole.id,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to create API key: ${error}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenResult = await this.apiKeyService.generateApiKeyToken(
|
||||
workspace.id,
|
||||
apiKey.id,
|
||||
expiresAt,
|
||||
);
|
||||
|
||||
if (!isDefined(tokenResult)) {
|
||||
this.logger.error('Failed to generate token.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`TOKEN:${tokenResult.token}\n`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user