Browse Source
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEPFu8Fz2BGGz/3nKpWMgKKqbHZeEFAmqmtpQACgkQWMgKKqbH ZeGSUA//a/6dX3Q2dbe8dWk46RZEBGuuzRlfo8JroLmIGEYhInuYg0z3fhIzxL27 UNRtq3w/y0t+NsqZxXjY+JKckrsuLPasmrftPzzxISjiptYHOz1/kS6ub0iTiJB8 woDtd9ApFrCuj0koTavVy7VMoH5LDaqgGw8hYdKF4yfGbp1NiJ2tzkri6ryBrEEw zk5P69hoZmJddHh0Ym4236piUc0YscZaWSsUR5NC1Mrtv1AGzyA/RKAZOrl/vPjD SpTwWvMyVNLliDWfBM598rYZx+Af0/k2HAZAk8jnMSWFx+DNQTqluEFaAHN0ukAr vlwrF08HyxTs3B/zXgFdk/zwvRPipi0wNTV0LLcptVc7GnvbyA80sZxnaSJajfJC iaJbUH35LoqE5QB8bBMIfoP6v+pu6IkGV8cz9lxhVIppv0EpGHdiOlsuPZepXMif kkKevQAiehGyTrmZm39kco3OQm11XTNmF0tTaubJc76tQEHVS97u0T8xwC0e7KT0 JgsczlMt5Wf/SzOPjrXLzRkGblt3WU+p0TakW19CTcELaZCM0cp+3SPvYbqJkii1 rKRsSwn+DdhWl1GniN8pIwTSwvwU0lkFxVvoGd/qx5Ctyg+6MBukKdmPvrr8F9jn tGkeCyUN6cPv1Wl/vTRiZqfZn8l+ZYX3hvGdgE0ZSxMBflnGisc= =SDWv -----END PGP SIGNATURE----- Merge tag '1.37.3' into public-api-member-group-crud Resolve the log_event conflict with upstream #7656, which made log_event take an EventType: keep the typed parameter and pass the acting user and device as Some(..), now that log_event_impl takes them as optional for the Public API events. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>pull/7569/head
98 changed files with 15536 additions and 23116 deletions
File diff suppressed because it is too large
@ -1,15 +1,31 @@ |
|||||
-- Dynamically create DROP FOREIGN KEY |
SELECT if ( |
||||
-- Some versions of MySQL or MariaDB might fail if the key doesn't exists |
EXISTS( |
||||
-- This checks if the key exists, and if so, will drop it. |
SELECT CONSTRAINT_NAME FROM information_schema.table_constraints |
||||
SET @drop_sso_fk = IF((SELECT true FROM information_schema.TABLE_CONSTRAINTS WHERE |
WHERE TABLE_SCHEMA = DATABASE() |
||||
CONSTRAINT_SCHEMA = DATABASE() AND |
AND TABLE_NAME = 'sso_users' |
||||
TABLE_NAME = 'sso_users' AND |
AND CONSTRAINT_TYPE = 'FOREIGN KEY' |
||||
CONSTRAINT_NAME = 'sso_users_ibfk_1' AND |
AND CONSTRAINT_NAME = 'sso_users_ibfk_1' |
||||
CONSTRAINT_TYPE = 'FOREIGN KEY') = true, |
) |
||||
'ALTER TABLE sso_users DROP FOREIGN KEY sso_users_ibfk_1', |
,'ALTER TABLE sso_users DROP FOREIGN KEY `sso_users_ibfk_1`' |
||||
'SELECT 1'); |
,'SELECT "info: FK sso_users_ibfk_1 does not exist."' |
||||
PREPARE stmt FROM @drop_sso_fk; |
) INTO @drop_stmt; |
||||
EXECUTE stmt; |
PREPARE drop_stmt FROM @drop_stmt; |
||||
DEALLOCATE PREPARE stmt; |
EXECUTE drop_stmt; |
||||
|
|
||||
|
SELECT if ( |
||||
|
EXISTS( |
||||
|
SELECT CONSTRAINT_NAME FROM information_schema.table_constraints |
||||
|
WHERE TABLE_SCHEMA = DATABASE() |
||||
|
AND TABLE_NAME = 'sso_users' |
||||
|
AND CONSTRAINT_TYPE = 'FOREIGN KEY' |
||||
|
AND CONSTRAINT_NAME = '1' |
||||
|
) |
||||
|
,'ALTER TABLE sso_users DROP FOREIGN KEY `1`' |
||||
|
,'SELECT "info: FK sso_users 1 does not exist."' |
||||
|
) INTO @drop_stmt; |
||||
|
PREPARE drop_stmt FROM @drop_stmt; |
||||
|
EXECUTE drop_stmt; |
||||
|
|
||||
|
DEALLOCATE PREPARE drop_stmt; |
||||
|
|
||||
ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE; |
ALTER TABLE sso_users ADD FOREIGN KEY(user_uuid) REFERENCES users(uuid) ON UPDATE CASCADE ON DELETE CASCADE; |
||||
|
|||||
File diff suppressed because it is too large
@ -0,0 +1,56 @@ |
|||||
|
import { test, expect, type Page, type TestInfo } from '@playwright/test'; |
||||
|
import * as OTPAuth from "otpauth"; |
||||
|
|
||||
|
import * as utils from "../global-utils"; |
||||
|
import { createAccount, logUser } from './setups/user'; |
||||
|
import { activateTOTP, disableTOTP } from './setups/2fa'; |
||||
|
|
||||
|
let users = utils.loadEnv(); |
||||
|
let totp; |
||||
|
|
||||
|
test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { |
||||
|
await utils.startVault(browser, testInfo, {}); |
||||
|
}); |
||||
|
|
||||
|
test.afterAll('Teardown', async ({}) => { |
||||
|
utils.stopVault(); |
||||
|
}); |
||||
|
|
||||
|
test('Change Key settings', async ({ page }) => { |
||||
|
await createAccount(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Change SHA-256 Iterations', async () => { |
||||
|
await page.getByRole('button', { name: 'Toggle collapse Settings' }).click(); |
||||
|
await page.getByRole('link', { name: 'Security' }).click(); |
||||
|
await page.getByRole('link', { name: 'Keys' }).click(); |
||||
|
|
||||
|
await page.getByRole('spinbutton', { name: 'KDF iterations * (required)'}).fill('700000'); |
||||
|
|
||||
|
await page.getByRole('button', { name: 'Update encryption settings' }).click(); |
||||
|
await page.getByRole('textbox', { name: 'Master password * (required)' }).fill(users.user1.password); |
||||
|
await page.getByRole('button', { name: 'Update settings' }).click(); |
||||
|
await page.getByRole('heading', { name: 'Log in' }).click(); |
||||
|
}); |
||||
|
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Switch to Argon2', async () => { |
||||
|
await page.getByRole('button', { name: 'Toggle collapse Settings' }).click(); |
||||
|
await page.getByRole('link', { name: 'Security' }).click(); |
||||
|
await page.getByRole('link', { name: 'Keys' }).click(); |
||||
|
|
||||
|
await page.locator('.ng-arrow-wrapper').click(); |
||||
|
await page.getByText('Argon2id').click(); |
||||
|
|
||||
|
await page.getByRole('spinbutton', { name: 'KDF memory (MB) * (required)'}).fill('16'); |
||||
|
await page.getByRole('spinbutton', { name: 'KDF iterations * (required)'}).fill('2'); |
||||
|
await page.getByRole('spinbutton', { name: 'KDF parallelism * (required)'}).fill('1'); |
||||
|
|
||||
|
await page.getByRole('button', { name: 'Update encryption settings' }).click(); |
||||
|
await page.getByRole('textbox', { name: 'Master password * (required)' }).fill(users.user1.password); |
||||
|
await page.getByRole('button', { name: 'Update settings' }).click(); |
||||
|
await page.getByRole('heading', { name: 'Log in' }).click(); |
||||
|
}); |
||||
|
|
||||
|
await logUser(test, page, users.user1); |
||||
|
}); |
||||
@ -0,0 +1,110 @@ |
|||||
|
import { test, expect, type Page, type TestInfo } from '@playwright/test'; |
||||
|
import * as OTPAuth from "otpauth"; |
||||
|
|
||||
|
import * as utils from "../global-utils"; |
||||
|
import { createAccount, logUser } from './setups/user'; |
||||
|
|
||||
|
let users = utils.loadEnv(); |
||||
|
let totp; |
||||
|
|
||||
|
test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { |
||||
|
await utils.startVault(browser, testInfo, {}); |
||||
|
|
||||
|
const context = await browser.newContext(); |
||||
|
const page = await context.newPage(); |
||||
|
await createAccount(test, page, users.user1); |
||||
|
await context.close(); |
||||
|
}); |
||||
|
|
||||
|
test.afterAll('Teardown', async ({}) => { |
||||
|
utils.stopVault(); |
||||
|
}); |
||||
|
|
||||
|
test('Password', async ({ context, page }, testInfo: TestInfo) => { |
||||
|
const label = 'Test Password'; |
||||
|
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Create password entry', async () => { |
||||
|
await page.getByRole('button', { name: 'New item' }).click(); |
||||
|
await page.getByRole('textbox', { name: 'Item name * (required)' }).fill(label); |
||||
|
await page.getByRole('textbox', { name: 'Username' }).fill(users.user1.name); |
||||
|
await page.getByRole('textbox', { name: 'Password' }).fill(users.user1.password); |
||||
|
await page.getByRole('button', { name: 'Save' }).click(); |
||||
|
await utils.checkNotification(page, 'Item added'); |
||||
|
await page.getByRole('button', { name: 'Close' }).click(); |
||||
|
}); |
||||
|
|
||||
|
// Log again
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Check', async () => { |
||||
|
await page.getByRole('row').filter({ hasText: label }).getByRole('button', { name: label }).click(); |
||||
|
await page.getByTestId('copy-username').click(); |
||||
|
await utils.checkNotification(page, 'Username copied'); |
||||
|
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(users.user1.name) |
||||
|
await page.getByTestId('copy-password').click(); |
||||
|
await utils.checkNotification(page, 'Password copied'); |
||||
|
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(users.user1.password) |
||||
|
await page.getByRole('button', { name: 'Close' }).click(); |
||||
|
}); |
||||
|
|
||||
|
await test.step('Delete', async () => { |
||||
|
await page.getByRole('row').filter({ hasText: label }).getByLabel('Options').click(); |
||||
|
await page.getByRole('menuitem', { name: 'Delete' }).click(); |
||||
|
await page.getByRole('button', { name: 'Yes' }).click(); |
||||
|
await utils.checkNotification(page, 'Item sent to bin'); |
||||
|
}); |
||||
|
|
||||
|
// Log again
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Deleted', async () => { |
||||
|
await expect(page.getByRole('row').filter({ hasText: label })).toHaveCount(0) |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
test('SSH Key', async ({ context, page }, testInfo: TestInfo) => { |
||||
|
const label = 'Test SSH key'; |
||||
|
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
const privateKey = await test.step('Create key entry', async () => { |
||||
|
await page.getByRole('button', { name: 'New', exact: true }).click(); |
||||
|
await page.getByRole('menuitem', { name: 'SSH key' }).click(); |
||||
|
await page.getByRole('textbox', { name: 'Item name * (required)' }).fill('Test SSH key'); |
||||
|
await page.getByRole('button', { name: 'Save' }).click(); |
||||
|
await utils.checkNotification(page, 'Item added'); |
||||
|
|
||||
|
await page.getByRole('button', { name: 'Copy private key' }).click(); |
||||
|
await utils.checkNotification(page, 'Private key copied'); |
||||
|
return await page.evaluate(() => navigator.clipboard.readText()); |
||||
|
}); |
||||
|
|
||||
|
// Log again
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Check', async () => { |
||||
|
await page.getByRole('row').filter({ hasText: label }).getByRole('button', { name: label }).click(); |
||||
|
|
||||
|
await page.getByRole('button', { name: 'Copy private key' }).click(); |
||||
|
await utils.checkNotification(page, 'Private key copied'); |
||||
|
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(privateKey) |
||||
|
await page.getByRole('button', { name: 'Close' }).click(); |
||||
|
}); |
||||
|
|
||||
|
await test.step('Delete', async () => { |
||||
|
await page.getByRole('row').filter({ hasText: label }).getByLabel('Options').click(); |
||||
|
await page.getByRole('menuitem', { name: 'Delete' }).click(); |
||||
|
await page.getByRole('button', { name: 'Yes' }).click(); |
||||
|
await utils.checkNotification(page, 'Item sent to bin'); |
||||
|
}); |
||||
|
|
||||
|
// Log again
|
||||
|
await logUser(test, page, users.user1); |
||||
|
|
||||
|
await test.step('Deleted', async () => { |
||||
|
await expect(page.getByRole('row').filter({ hasText: label })).toHaveCount(0) |
||||
|
}) |
||||
|
}); |
||||
@ -0,0 +1,21 @@ |
|||||
|
import { expect, type Browser, Page } from '@playwright/test'; |
||||
|
import * as utils from '../../global-utils'; |
||||
|
|
||||
|
utils.loadEnv(); |
||||
|
|
||||
|
export async function login(test, page: Page) { |
||||
|
await test.step(`Admin login`, async () => { |
||||
|
await page.goto('/admin'); |
||||
|
await page.getByRole('textbox', { name: 'Enter admin token' }).fill(process.env.ADMIN_TOKEN); |
||||
|
await page.getByRole('button', { name: 'Enter' }).click(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export async function invite(test, page: Page, email: string) { |
||||
|
await test.step(`Invite user with ${email}`, async () => { |
||||
|
await page.getByRole('link', { name: 'Users' }).click(); |
||||
|
await page.getByRole('textbox', { name: 'Enter email' }).fill(email); |
||||
|
await page.getByRole('button', { name: 'Invite' }).click(); |
||||
|
await expect(page.getByRole('row', { name: email })).toHaveText(/Invited/); |
||||
|
}); |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
[toolchain] |
[toolchain] |
||||
channel = "1.97.1" |
channel = "1.98.1" |
||||
components = [ "rustfmt", "clippy" ] |
components = [ "rustfmt", "clippy" ] |
||||
profile = "minimal" |
profile = "minimal" |
||||
|
|||||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,12 @@ |
|||||
|
Admin account recovery from {{org_name}} organization |
||||
|
<!----------------> |
||||
|
{{#if reset_password}} |
||||
|
The master password for {{user_name}} has been changed. |
||||
|
{{/if}} |
||||
|
{{#if reset_2fa}} |
||||
|
Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} |
||||
|
{{/if}} |
||||
|
|
||||
|
If you did not initiate this request, please reach out to your administrator immediately. |
||||
|
|
||||
|
{{> email/email_footer_text }} |
||||
@ -1,10 +1,17 @@ |
|||||
Master Password Has Been Changed |
Admin account recovery from {{org_name}} organization |
||||
<!----------------> |
<!----------------> |
||||
{{> email/email_header }} |
{{> email/email_header }} |
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> |
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> |
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> |
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> |
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top"> |
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top"> |
||||
The master password for <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{user_name}}</b> has been changed by an administrator in your <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{org_name}}</b> organization. If you did not initiate this request, please reach out to your administrator immediately. |
{{#if reset_password}} |
||||
|
The master password for <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{user_name}}</b> has been changed. |
||||
|
{{/if}} |
||||
|
{{#if reset_2fa}} |
||||
|
Your two-step verification providers have been reset.{{#if fallback_2fa_email}} Email two factor has been activated as a fallback.{{/if}} |
||||
|
{{/if}} |
||||
|
<br> |
||||
|
If you did not initiate this request, please reach out to your administrator immediately. |
||||
</td> |
</td> |
||||
</tr> |
</tr> |
||||
</table> |
</table> |
||||
@ -1,4 +0,0 @@ |
|||||
Master Password Has Been Changed |
|
||||
<!----------------> |
|
||||
The master password for {{user_name}} has been changed by an administrator in your {{org_name}} organization. If you did not initiate this request, please reach out to your administrator immediately. |
|
||||
{{> email/email_footer_text }} |
|
||||
Loading…
Reference in new issue