feat(core): Add user email parameter when activating EULA license (#23350)

Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Csaba Tuncsik
2026-01-15 09:54:26 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.5
parent 5567b91f6c
commit 3d71a6d9d9
7 changed files with 56 additions and 21 deletions
+1 -1
View File
@@ -112,7 +112,7 @@
"@n8n/typeorm": "catalog:",
"@n8n/utils": "workspace:*",
"@n8n_io/ai-assistant-sdk": "catalog:",
"@n8n_io/license-sdk": "2.24.1",
"@n8n_io/license-sdk": "2.25.0",
"@parcel/watcher": "^2.5.1",
"@rudderstack/rudder-sdk-node": "3.0.0",
"@sentry/node": "catalog:",
+12 -5
View File
@@ -101,17 +101,24 @@ describe('License', () => {
);
});
test('attempts to activate license with provided key', async () => {
test('attempts to activate license with provided key (initial activation)', async () => {
await license.activate(MOCK_ACTIVATION_KEY);
expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY, undefined);
expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY, {
eulaUri: undefined,
email: undefined,
});
});
test('attempts to activate license with eulaUri', async () => {
test('attempts to activate license with eulaUri and userEmail (EULA acceptance)', async () => {
const eulaUri = 'https://n8n.io/legal/eula/';
await license.activate(MOCK_ACTIVATION_KEY, eulaUri);
const userEmail = 'user@example.com';
await license.activate(MOCK_ACTIVATION_KEY, eulaUri, userEmail);
expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY, eulaUri);
expect(LicenseManager.prototype.activate).toHaveBeenCalledWith(MOCK_ACTIVATION_KEY, {
eulaUri,
email: userEmail,
});
});
test('renews license', async () => {
+4 -2
View File
@@ -198,12 +198,14 @@ export class License implements LicenseProvider {
}
}
async activate(activationKey: string, eulaUri?: string): Promise<void> {
async activate(activationKey: string): Promise<void>;
async activate(activationKey: string, eulaUri: string, userEmail: string): Promise<void>;
async activate(activationKey: string, eulaUri?: string, userEmail?: string): Promise<void> {
if (!this.manager) {
return;
}
await this.manager.activate(activationKey, eulaUri);
await this.manager.activate(activationKey, { eulaUri, email: userEmail });
this.logger.debug('License activated');
}
@@ -65,16 +65,24 @@ describe('LicenseService', () => {
});
describe('activateLicense', () => {
it('should activate license without eulaUri', async () => {
it('should activate license without eulaUri (initial activation)', async () => {
license.activate.mockResolvedValueOnce();
await licenseService.activateLicense('activation-key');
expect(license.activate).toHaveBeenCalledWith('activation-key', undefined);
expect(license.activate).toHaveBeenCalledWith('activation-key');
});
it('should activate license with eulaUri', async () => {
it('should activate license with eulaUri and userEmail (EULA acceptance)', async () => {
license.activate.mockResolvedValueOnce();
await licenseService.activateLicense('activation-key', 'https://n8n.io/legal/eula/');
expect(license.activate).toHaveBeenCalledWith('activation-key', 'https://n8n.io/legal/eula/');
await licenseService.activateLicense(
'activation-key',
'https://n8n.io/legal/eula/',
'user@example.com',
);
expect(license.activate).toHaveBeenCalledWith(
'activation-key',
'https://n8n.io/legal/eula/',
'user@example.com',
);
});
it('should throw LicenseEulaRequiredError when EULA_REQUIRED error occurs', async () => {
@@ -59,7 +59,11 @@ export class LicenseController {
@GlobalScope('license:manage')
async activateLicense(req: LicenseRequest.Activate) {
const { activationKey, eulaUri } = req.body;
await this.licenseService.activateLicense(activationKey, eulaUri);
if (eulaUri) {
await this.licenseService.activateLicense(activationKey, eulaUri, req.user.email);
} else {
await this.licenseService.activateLicense(activationKey);
}
return await this.getTokenAndData();
}
+16 -2
View File
@@ -109,9 +109,23 @@ export class LicenseService {
return this.license.getManagementJwt();
}
async activateLicense(activationKey: string, eulaUri?: string) {
// Overload signatures
async activateLicense(activationKey: string): Promise<void>;
async activateLicense(activationKey: string, eulaUri: string, userEmail: string): Promise<void>;
// Implementation signature
async activateLicense(
activationKey: string,
eulaUri?: string,
userEmail?: string,
): Promise<void> {
try {
await this.license.activate(activationKey, eulaUri);
if (eulaUri && userEmail) {
await this.license.activate(activationKey, eulaUri, userEmail);
} else if (!eulaUri && !userEmail) {
await this.license.activate(activationKey);
} else {
throw new BadRequestError('When providing eulaUri, userEmail is required');
}
} catch (e) {
// Check if this is a EULA_REQUIRED error from license server
if (this.isEulaRequiredError(e)) {
+5 -5
View File
@@ -1680,8 +1680,8 @@ importers:
specifier: 'catalog:'
version: 1.20.0
'@n8n_io/license-sdk':
specifier: 2.24.1
version: 2.24.1
specifier: 2.25.0
version: 2.25.0
'@parcel/watcher':
specifier: ^2.5.1
version: 2.5.1
@@ -6520,8 +6520,8 @@ packages:
resolution: {integrity: sha512-uXa6EDufQIV46tvqQ222441dWZYLnT219HC5H8grfAVd6Mpp2Rs4NoBM6YPWnGFWBp8XVsgXUud0EKRbJsw0eA==}
engines: {node: '>=20.15', pnpm: '>=8.14'}
'@n8n_io/license-sdk@2.24.1':
resolution: {integrity: sha512-nrITEzOmFonFXD5XtzHjpY/s1kpKksYQoY5HBqhzRkGq3ldEO3nLKDl6RG9AEtkKKlKkid3lzviZJCIDINZlQw==}
'@n8n_io/license-sdk@2.25.0':
resolution: {integrity: sha512-6pNo09XHhBb+igaHfpuvLez+IxcerzRWMFUH1eqAOOE0Uf5orQp8j5p5RE7ohNZYtxlMoL/PcsQIXK5uHBycwg==}
engines: {node: '>=18.12.1'}
'@n8n_io/riot-tmpl@4.0.1':
@@ -23037,7 +23037,7 @@ snapshots:
'@n8n_io/ai-assistant-sdk@1.20.0': {}
'@n8n_io/license-sdk@2.24.1':
'@n8n_io/license-sdk@2.25.0':
dependencies:
crypto-js: 4.2.0
node-machine-id: 1.1.12