mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(core): introduce JWT API keys for the public API (#11005)
This commit is contained in:
parent
6a722c45ea
commit
679fa4a10a
|
@ -1,66 +1,88 @@
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { randomString } from 'n8n-workflow';
|
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
|
||||||
import type { ApiKey } from '@/databases/entities/api-key';
|
import type { ApiKey } from '@/databases/entities/api-key';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
|
import { EventService } from '@/events/event.service';
|
||||||
import type { ApiKeysRequest, AuthenticatedRequest } from '@/requests';
|
import type { ApiKeysRequest, AuthenticatedRequest } from '@/requests';
|
||||||
import { API_KEY_PREFIX } from '@/services/public-api-key.service';
|
import { PublicApiKeyService } from '@/services/public-api-key.service';
|
||||||
import { mockInstance } from '@test/mocking';
|
import { mockInstance } from '@test/mocking';
|
||||||
|
|
||||||
import { ApiKeysController } from '../api-keys.controller';
|
import { ApiKeysController } from '../api-keys.controller';
|
||||||
|
|
||||||
describe('ApiKeysController', () => {
|
describe('ApiKeysController', () => {
|
||||||
const apiKeysRepository = mockInstance(ApiKeyRepository);
|
const publicApiKeyService = mockInstance(PublicApiKeyService);
|
||||||
|
const eventService = mockInstance(EventService);
|
||||||
const controller = Container.get(ApiKeysController);
|
const controller = Container.get(ApiKeysController);
|
||||||
|
|
||||||
let req: AuthenticatedRequest;
|
let req: AuthenticatedRequest;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
req = mock<AuthenticatedRequest>({ user: mock<User>({ id: '123' }) });
|
req = { user: { id: '123' } } as AuthenticatedRequest;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createAPIKey', () => {
|
describe('createAPIKey', () => {
|
||||||
it('should create and save an API key', async () => {
|
it('should create and save an API key', async () => {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
const apiKeyData = {
|
const apiKeyData = {
|
||||||
id: '123',
|
id: '123',
|
||||||
userId: '123',
|
userId: '123',
|
||||||
label: 'My API Key',
|
label: 'My API Key',
|
||||||
apiKey: `${API_KEY_PREFIX}${randomString(42)}`,
|
apiKey: 'apiKey********',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
} as ApiKey;
|
} as ApiKey;
|
||||||
|
|
||||||
apiKeysRepository.upsert.mockImplementation();
|
const req = mock<AuthenticatedRequest>({ user: mock<User>({ id: '123' }) });
|
||||||
|
|
||||||
apiKeysRepository.findOneByOrFail.mockResolvedValue(apiKeyData);
|
publicApiKeyService.createPublicApiKeyForUser.mockResolvedValue(apiKeyData);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
const newApiKey = await controller.createAPIKey(req);
|
const newApiKey = await controller.createAPIKey(req);
|
||||||
|
|
||||||
expect(apiKeysRepository.upsert).toHaveBeenCalled();
|
// Assert
|
||||||
|
|
||||||
|
expect(publicApiKeyService.createPublicApiKeyForUser).toHaveBeenCalled();
|
||||||
expect(apiKeyData).toEqual(newApiKey);
|
expect(apiKeyData).toEqual(newApiKey);
|
||||||
|
expect(eventService.emit).toHaveBeenCalledWith(
|
||||||
|
'public-api-key-created',
|
||||||
|
expect.objectContaining({ user: req.user, publicApi: false }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAPIKeys', () => {
|
describe('getAPIKeys', () => {
|
||||||
it('should return the users api keys redacted', async () => {
|
it('should return the users api keys redacted', async () => {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
const apiKeyData = {
|
const apiKeyData = {
|
||||||
id: '123',
|
id: '123',
|
||||||
userId: '123',
|
userId: '123',
|
||||||
label: 'My API Key',
|
label: 'My API Key',
|
||||||
apiKey: `${API_KEY_PREFIX}${randomString(42)}`,
|
apiKey: 'apiKey***',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
} as ApiKey;
|
} as ApiKey;
|
||||||
|
|
||||||
apiKeysRepository.findBy.mockResolvedValue([apiKeyData]);
|
publicApiKeyService.getRedactedApiKeysForUser.mockResolvedValue([apiKeyData]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
const apiKeys = await controller.getAPIKeys(req);
|
const apiKeys = await controller.getAPIKeys(req);
|
||||||
expect(apiKeys[0].apiKey).not.toEqual(apiKeyData.apiKey);
|
|
||||||
expect(apiKeysRepository.findBy).toHaveBeenCalledWith({ userId: req.user.id });
|
// Assert
|
||||||
|
|
||||||
|
expect(apiKeys).toEqual([apiKeyData]);
|
||||||
|
expect(publicApiKeyService.getRedactedApiKeysForUser).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: req.user.id }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteAPIKey', () => {
|
describe('deleteAPIKey', () => {
|
||||||
it('should delete the API key', async () => {
|
it('should delete the API key', async () => {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
const user = mock<User>({
|
const user = mock<User>({
|
||||||
id: '123',
|
id: '123',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
|
@ -68,12 +90,22 @@ describe('ApiKeysController', () => {
|
||||||
role: 'global:member',
|
role: 'global:member',
|
||||||
mfaEnabled: false,
|
mfaEnabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const req = mock<ApiKeysRequest.DeleteAPIKey>({ user, params: { id: user.id } });
|
const req = mock<ApiKeysRequest.DeleteAPIKey>({ user, params: { id: user.id } });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
await controller.deleteAPIKey(req);
|
await controller.deleteAPIKey(req);
|
||||||
expect(apiKeysRepository.delete).toHaveBeenCalledWith({
|
|
||||||
userId: req.user.id,
|
publicApiKeyService.deleteApiKeyForUser.mockResolvedValue();
|
||||||
id: req.params.id,
|
|
||||||
});
|
// Assert
|
||||||
|
|
||||||
|
expect(publicApiKeyService.deleteApiKeyForUser).toHaveBeenCalledWith(user, user.id);
|
||||||
|
expect(eventService.emit).toHaveBeenCalledWith(
|
||||||
|
'public-api-key-deleted',
|
||||||
|
expect.objectContaining({ user, publicApi: false }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,16 +3,13 @@ import type { Router } from 'express';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import type { HttpError } from 'express-openapi-validator/dist/framework/types';
|
import type { HttpError } from 'express-openapi-validator/dist/framework/types';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import type { OpenAPIV3 } from 'openapi-types';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { JsonObject } from 'swagger-ui-express';
|
import type { JsonObject } from 'swagger-ui-express';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import YAML from 'yamljs';
|
import YAML from 'yamljs';
|
||||||
|
|
||||||
import { EventService } from '@/events/event.service';
|
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
import type { AuthenticatedRequest } from '@/requests';
|
|
||||||
import { PublicApiKeyService } from '@/services/public-api-key.service';
|
import { PublicApiKeyService } from '@/services/public-api-key.service';
|
||||||
import { UrlService } from '@/services/url.service';
|
import { UrlService } from '@/services/url.service';
|
||||||
|
|
||||||
|
@ -85,28 +82,7 @@ async function createApiRouter(
|
||||||
},
|
},
|
||||||
validateSecurity: {
|
validateSecurity: {
|
||||||
handlers: {
|
handlers: {
|
||||||
ApiKeyAuth: async (
|
ApiKeyAuth: Container.get(PublicApiKeyService).getAuthMiddleware(version),
|
||||||
req: AuthenticatedRequest,
|
|
||||||
_scopes: unknown,
|
|
||||||
schema: OpenAPIV3.ApiKeySecurityScheme,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const providedApiKey = req.headers[schema.name.toLowerCase()] as string;
|
|
||||||
|
|
||||||
const user = await Container.get(PublicApiKeyService).getUserForApiKey(providedApiKey);
|
|
||||||
|
|
||||||
if (!user) return false;
|
|
||||||
|
|
||||||
Container.get(EventService).emit('public-api-invoked', {
|
|
||||||
userId: user.id,
|
|
||||||
path: req.path,
|
|
||||||
method: req.method,
|
|
||||||
apiVersion: version,
|
|
||||||
});
|
|
||||||
|
|
||||||
req.user = user;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type { InstanceSettings } from 'n8n-core';
|
||||||
|
import type { OpenAPIV3 } from 'openapi-types';
|
||||||
|
|
||||||
|
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
|
||||||
|
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||||
|
import { getConnection } from '@/db';
|
||||||
|
import type { EventService } from '@/events/event.service';
|
||||||
|
import type { AuthenticatedRequest } from '@/requests';
|
||||||
|
import { createOwnerWithApiKey } from '@test-integration/db/users';
|
||||||
|
import * as testDb from '@test-integration/test-db';
|
||||||
|
|
||||||
|
import { JwtService } from '../jwt.service';
|
||||||
|
import { PublicApiKeyService } from '../public-api-key.service';
|
||||||
|
|
||||||
|
const mockReqWith = (apiKey: string, path: string, method: string) => {
|
||||||
|
return mock<AuthenticatedRequest>({
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'x-n8n-api-key': apiKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const instanceSettings = mock<InstanceSettings>({ encryptionKey: 'test-key' });
|
||||||
|
|
||||||
|
const eventService = mock<EventService>();
|
||||||
|
|
||||||
|
const securitySchema = mock<OpenAPIV3.ApiKeySecurityScheme>({
|
||||||
|
name: 'X-N8N-API-KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
const jwtService = new JwtService(instanceSettings);
|
||||||
|
|
||||||
|
let userRepository: UserRepository;
|
||||||
|
let apiKeyRepository: ApiKeyRepository;
|
||||||
|
|
||||||
|
describe('PublicApiKeyService', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await testDb.truncate(['User']);
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await testDb.init();
|
||||||
|
userRepository = new UserRepository(getConnection());
|
||||||
|
apiKeyRepository = new ApiKeyRepository(getConnection());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testDb.terminate();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAuthMiddleware', () => {
|
||||||
|
it('should return false if api key is invalid', async () => {
|
||||||
|
//Arrange
|
||||||
|
|
||||||
|
const apiKey = 'invalid';
|
||||||
|
const path = '/test';
|
||||||
|
const method = 'GET';
|
||||||
|
const apiVersion = 'v1';
|
||||||
|
|
||||||
|
const publicApiKeyService = new PublicApiKeyService(
|
||||||
|
apiKeyRepository,
|
||||||
|
userRepository,
|
||||||
|
jwtService,
|
||||||
|
eventService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const middleware = publicApiKeyService.getAuthMiddleware(apiVersion);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
|
||||||
|
const response = await middleware(mockReqWith(apiKey, path, method), {}, securitySchema);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
|
||||||
|
expect(response).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if valid api key is not in database', async () => {
|
||||||
|
//Arrange
|
||||||
|
|
||||||
|
const apiKey = jwtService.sign({ sub: '123' });
|
||||||
|
const path = '/test';
|
||||||
|
const method = 'GET';
|
||||||
|
const apiVersion = 'v1';
|
||||||
|
|
||||||
|
const publicApiKeyService = new PublicApiKeyService(
|
||||||
|
apiKeyRepository,
|
||||||
|
userRepository,
|
||||||
|
jwtService,
|
||||||
|
eventService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const middleware = publicApiKeyService.getAuthMiddleware(apiVersion);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
|
||||||
|
const response = await middleware(mockReqWith(apiKey, path, method), {}, securitySchema);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
|
||||||
|
expect(response).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if valid api key exist in the database', async () => {
|
||||||
|
//Arrange
|
||||||
|
|
||||||
|
const path = '/test';
|
||||||
|
const method = 'GET';
|
||||||
|
const apiVersion = 'v1';
|
||||||
|
|
||||||
|
const publicApiKeyService = new PublicApiKeyService(
|
||||||
|
apiKeyRepository,
|
||||||
|
userRepository,
|
||||||
|
jwtService,
|
||||||
|
eventService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const owner = await createOwnerWithApiKey();
|
||||||
|
|
||||||
|
const [{ apiKey }] = owner.apiKeys;
|
||||||
|
|
||||||
|
const middleware = publicApiKeyService.getAuthMiddleware(apiVersion);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
|
||||||
|
const response = await middleware(mockReqWith(apiKey, path, method), {}, securitySchema);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
|
||||||
|
expect(response).toBe(true);
|
||||||
|
expect(eventService.emit).toHaveBeenCalledTimes(1);
|
||||||
|
expect(eventService.emit).toHaveBeenCalledWith(
|
||||||
|
'public-api-invoked',
|
||||||
|
expect.objectContaining({
|
||||||
|
userId: owner.id,
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
apiVersion: 'v1',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,16 +1,28 @@
|
||||||
import { randomBytes } from 'node:crypto';
|
import type { OpenAPIV3 } from 'openapi-types';
|
||||||
import Container, { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
import { ApiKey } from '@/databases/entities/api-key';
|
import { ApiKey } from '@/databases/entities/api-key';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
|
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
|
||||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||||
|
import { EventService } from '@/events/event.service';
|
||||||
|
import type { AuthenticatedRequest } from '@/requests';
|
||||||
|
|
||||||
export const API_KEY_PREFIX = 'n8n_api_';
|
import { JwtService } from './jwt.service';
|
||||||
|
|
||||||
|
const API_KEY_AUDIENCE = 'public-api';
|
||||||
|
const API_KEY_ISSUER = 'n8n';
|
||||||
|
const REDACT_API_KEY_REVEAL_COUNT = 15;
|
||||||
|
const REDACT_API_KEY_MAX_LENGTH = 80;
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PublicApiKeyService {
|
export class PublicApiKeyService {
|
||||||
constructor(private readonly apiKeyRepository: ApiKeyRepository) {}
|
constructor(
|
||||||
|
private readonly apiKeyRepository: ApiKeyRepository,
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
private readonly eventService: EventService,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new public API key for the specified user.
|
* Creates a new public API key for the specified user.
|
||||||
|
@ -18,7 +30,7 @@ export class PublicApiKeyService {
|
||||||
* @returns A promise that resolves to the newly created API key.
|
* @returns A promise that resolves to the newly created API key.
|
||||||
*/
|
*/
|
||||||
async createPublicApiKeyForUser(user: User) {
|
async createPublicApiKeyForUser(user: User) {
|
||||||
const apiKey = this.createApiKeyString();
|
const apiKey = this.generateApiKey(user);
|
||||||
await this.apiKeyRepository.upsert(
|
await this.apiKeyRepository.upsert(
|
||||||
this.apiKeyRepository.create({
|
this.apiKeyRepository.create({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -48,8 +60,8 @@ export class PublicApiKeyService {
|
||||||
await this.apiKeyRepository.delete({ userId: user.id, id: apiKeyId });
|
await this.apiKeyRepository.delete({ userId: user.id, id: apiKeyId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserForApiKey(apiKey: string) {
|
private async getUserForApiKey(apiKey: string) {
|
||||||
return await Container.get(UserRepository)
|
return await this.userRepository
|
||||||
.createQueryBuilder('user')
|
.createQueryBuilder('user')
|
||||||
.innerJoin(ApiKey, 'apiKey', 'apiKey.userId = user.id')
|
.innerJoin(ApiKey, 'apiKey', 'apiKey.userId = user.id')
|
||||||
.where('apiKey.apiKey = :apiKey', { apiKey })
|
.where('apiKey.apiKey = :apiKey', { apiKey })
|
||||||
|
@ -68,13 +80,39 @@ export class PublicApiKeyService {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
redactApiKey(apiKey: string) {
|
redactApiKey(apiKey: string) {
|
||||||
const keepLength = 5;
|
const visiblePart = apiKey.slice(0, REDACT_API_KEY_REVEAL_COUNT);
|
||||||
return (
|
const redactedPart = '*'.repeat(apiKey.length - REDACT_API_KEY_REVEAL_COUNT);
|
||||||
API_KEY_PREFIX +
|
|
||||||
apiKey.slice(API_KEY_PREFIX.length, API_KEY_PREFIX.length + keepLength) +
|
const completeRedactedApiKey = visiblePart + redactedPart;
|
||||||
'*'.repeat(apiKey.length - API_KEY_PREFIX.length - keepLength)
|
|
||||||
);
|
return completeRedactedApiKey.slice(0, REDACT_API_KEY_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
createApiKeyString = () => `${API_KEY_PREFIX}${randomBytes(40).toString('hex')}`;
|
getAuthMiddleware(version: string) {
|
||||||
|
return async (
|
||||||
|
req: AuthenticatedRequest,
|
||||||
|
_scopes: unknown,
|
||||||
|
schema: OpenAPIV3.ApiKeySecurityScheme,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const providedApiKey = req.headers[schema.name.toLowerCase()] as string;
|
||||||
|
|
||||||
|
const user = await this.getUserForApiKey(providedApiKey);
|
||||||
|
|
||||||
|
if (!user) return false;
|
||||||
|
|
||||||
|
this.eventService.emit('public-api-invoked', {
|
||||||
|
userId: user.id,
|
||||||
|
path: req.path,
|
||||||
|
method: req.method,
|
||||||
|
apiVersion: version,
|
||||||
|
});
|
||||||
|
|
||||||
|
req.user = user;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateApiKey = (user: User) =>
|
||||||
|
this.jwtService.sign({ sub: user.id, iss: API_KEY_ISSUER, aud: API_KEY_AUDIENCE });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { hash } from 'bcryptjs';
|
import { hash } from 'bcryptjs';
|
||||||
import { randomString } from 'n8n-workflow';
|
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
|
|
||||||
import { AuthIdentity } from '@/databases/entities/auth-identity';
|
import { AuthIdentity } from '@/databases/entities/auth-identity';
|
||||||
import { type GlobalRole, type User } from '@/databases/entities/user';
|
import { type GlobalRole, type User } from '@/databases/entities/user';
|
||||||
import { ApiKeyRepository } from '@/databases/repositories/api-key.repository';
|
|
||||||
import { AuthIdentityRepository } from '@/databases/repositories/auth-identity.repository';
|
import { AuthIdentityRepository } from '@/databases/repositories/auth-identity.repository';
|
||||||
import { AuthUserRepository } from '@/databases/repositories/auth-user.repository';
|
import { AuthUserRepository } from '@/databases/repositories/auth-user.repository';
|
||||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||||
import { MfaService } from '@/mfa/mfa.service';
|
import { MfaService } from '@/mfa/mfa.service';
|
||||||
import { TOTPService } from '@/mfa/totp.service';
|
import { TOTPService } from '@/mfa/totp.service';
|
||||||
|
import { PublicApiKeyService } from '@/services/public-api-key.service';
|
||||||
|
|
||||||
import { randomApiKey, randomEmail, randomName, randomValidPassword } from '../random';
|
import { randomEmail, randomName, randomValidPassword } from '../random';
|
||||||
|
|
||||||
// pre-computed bcrypt hash for the string 'password', using `await hash('password', 10)`
|
// pre-computed bcrypt hash for the string 'password', using `await hash('password', 10)`
|
||||||
const passwordHash = '$2a$10$njedH7S6V5898mj6p0Jr..IGY9Ms.qNwR7RbSzzX9yubJocKfvGGK';
|
const passwordHash = '$2a$10$njedH7S6V5898mj6p0Jr..IGY9Ms.qNwR7RbSzzX9yubJocKfvGGK';
|
||||||
|
@ -81,17 +80,8 @@ export async function createUserWithMfaEnabled(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const createApiKeyEntity = (user: User) => {
|
|
||||||
const apiKey = randomApiKey();
|
|
||||||
return Container.get(ApiKeyRepository).create({
|
|
||||||
userId: user.id,
|
|
||||||
label: randomString(10),
|
|
||||||
apiKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addApiKey = async (user: User) => {
|
export const addApiKey = async (user: User) => {
|
||||||
return await Container.get(ApiKeyRepository).save(createApiKeyEntity(user));
|
return await Container.get(PublicApiKeyService).createPublicApiKeyForUser(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createOwnerWithApiKey() {
|
export async function createOwnerWithApiKey() {
|
||||||
|
|
Loading…
Reference in a new issue