feat(users): passwordless user creation via emailed set-password link, display full name instead of username
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

Admins now create users with a name + email (no password) and role;
a Send Email action (per-user, also reusable for resets) issues a
one-time token and POSTs it to an admin-configurable webhook URL —
intended for a Power Automate flow that delivers the actual email.
The old admin-sets-the-password flow is kept server-side
(LegacyUserCreateWithPassword) but no longer wired into the UI.

Every user-facing surface (top bar, sidebar, user management, audit
log, assignee pickers, dispute notifications) now shows full_name
instead of username, falling back to username when unset.

Also fixes long attack_procedure/expected_detection/suggested_text
text overflowing its container in the template review panels and
Red/Blue team fields (missing break-words on whitespace-pre-wrap
blocks).
This commit is contained in:
kitos
2026-07-17 16:58:25 +02:00
parent 60ab2e558f
commit 4dea19cae9
36 changed files with 974 additions and 159 deletions
+1
View File
@@ -4,6 +4,7 @@ export interface AuditLogOut {
id: string;
user_id: string | null;
username: string | null;
full_name: string | null;
action: string;
entity_type: string | null;
entity_id: string | null;
+16
View File
@@ -50,3 +50,19 @@ export async function changePassword(
new_password: newPassword,
});
}
/** Check a set-password token before rendering the form. Public — no auth. */
export async function validateSetPasswordToken(
token: string,
): Promise<{ valid: boolean; full_name: string | null }> {
const { data } = await client.get<{ valid: boolean; full_name: string | null }>(
"/auth/set-password/validate",
{ params: { token } },
);
return data;
}
/** Complete a password setup/reset using a one-time token. Public — no auth. */
export async function setPassword(token: string, newPassword: string): Promise<void> {
await client.post("/auth/set-password", { token, new_password: newPassword });
}
+16
View File
@@ -130,6 +130,7 @@ export interface UserPreferencesUpdate {
export interface UserMeOut {
id: string;
username: string;
full_name: string | null;
email: string | null;
role: string;
is_active: boolean;
@@ -198,6 +199,21 @@ export async function testJiraConnection(): Promise<{
return data;
}
export interface PasswordWebhookConfigOut {
configured: boolean;
url: string;
}
export async function getPasswordWebhookConfig(): Promise<PasswordWebhookConfigOut> {
const { data } = await client.get<PasswordWebhookConfigOut>("/system/password-webhook-config");
return data;
}
export async function updatePasswordWebhookConfig(url: string): Promise<PasswordWebhookConfigOut> {
const { data } = await client.patch<PasswordWebhookConfigOut>("/system/password-webhook-config", { url });
return data;
}
export async function testTempoConnection(): Promise<{
status: "ok" | "error" | "disabled";
message?: string;
+1
View File
@@ -475,6 +475,7 @@ export async function requestDiscussion(testId: string): Promise<{
status: string;
message: string;
rejector_username: string;
rejector_full_name: string | null;
rejector_email: string | null;
rejector_role: string;
}> {
+15 -4
View File
@@ -3,24 +3,28 @@ import client from "./client";
export interface UserOut {
id: string;
username: string;
full_name: string | null;
email: string | null;
role: string;
is_active: boolean;
must_change_password: boolean;
created_at: string | null;
last_login: string | null;
}
export interface UserCreatePayload {
username: string;
email?: string;
password: string;
full_name: string;
email: string;
role: string;
}
export interface UserUpdatePayload {
email?: string;
full_name?: string;
role?: string;
is_active?: boolean;
// Not sent by the current UI — the set-password-email flow replaces
// admin-set passwords — but still supported server-side.
password?: string;
}
@@ -33,6 +37,7 @@ export async function getUsers(): Promise<UserOut[]> {
export interface OperatorOut {
id: string;
username: string;
full_name: string | null;
role: string;
}
@@ -42,7 +47,7 @@ export async function getOperators(): Promise<OperatorOut[]> {
return data;
}
/** Create a new user (admin only). */
/** Create a new user (admin only). No password — see sendPasswordEmail(). */
export async function createUser(payload: UserCreatePayload): Promise<UserOut> {
const { data } = await client.post<UserOut>("/users", payload);
return data;
@@ -53,3 +58,9 @@ export async function updateUser(userId: string, payload: UserUpdatePayload): Pr
const { data } = await client.patch<UserOut>(`/users/${userId}`, payload);
return data;
}
/** Email a one-time set-password link to a user (admin only). Also used for password resets. */
export async function sendPasswordEmail(userId: string): Promise<{ detail: string }> {
const { data } = await client.post<{ detail: string }>(`/users/${userId}/send-password-email`);
return data;
}