diff --git a/docs/demos/2572-reset-password-demo.png b/docs/demos/2572-reset-password-demo.png
new file mode 100644
index 0000000000..ddd081fdfc
Binary files /dev/null and b/docs/demos/2572-reset-password-demo.png differ
diff --git a/web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx b/web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
index 0bd19ebbdf..bf88a97cbf 100644
--- a/web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
+++ b/web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
@@ -3,7 +3,7 @@ import {useState} from "react"
import type {User} from "@agenta/shared/types"
import {message} from "@agenta/ui/app-message"
import {EditOutlined, MoreOutlined, SyncOutlined} from "@ant-design/icons"
-import {ArrowClockwise, Trash} from "@phosphor-icons/react"
+import {ArrowClockwise, Key, Trash} from "@phosphor-icons/react"
import {Button, Dropdown, Input, Modal, Space, Tag, Tooltip, Typography} from "antd"
import AlertPopup from "@/oss/components/AlertPopup/AlertPopup"
@@ -11,7 +11,7 @@ import {useWorkspacePermissions} from "@/oss/hooks/useWorkspacePermissions"
import {isEmailInvitationsEnabled} from "@/oss/lib/helpers/isEE"
import {snakeToTitle} from "@/oss/lib/helpers/utils"
import {WorkspaceMember} from "@/oss/lib/Types"
-import {updateUsername} from "@/oss/services/profile"
+import {resetPassword, updateUsername} from "@/oss/services/profile"
import {
assignWorkspaceRole,
removeFromWorkspace,
@@ -22,6 +22,9 @@ import {useOrgData} from "@/oss/state/org"
import {useProfileData} from "@/oss/state/profile"
import {useWorkspaceRoles} from "@/oss/state/workspace"
+import GenerateResetLinkModal from "./Modals/GenerateResetLinkModal"
+import PasswordResetLinkModal from "./Modals/PasswordResetLinkModal"
+
export const Actions: React.FC<{
member: WorkspaceMember
hidden?: boolean
@@ -39,6 +42,13 @@ export const Actions: React.FC<{
const {refetch: refetchProfile} = useProfileData()
const [renameOpen, setRenameOpen] = useState(false)
const [renameValue, setRenameValue] = useState(user.username || "")
+ const [generateResetLinkOpen, setGenerateResetLinkOpen] = useState(false)
+ const [resetLinkOpen, setResetLinkOpen] = useState(false)
+ const [resetLink, setResetLink] = useState("")
+ const [resetLoading, setResetLoading] = useState(false)
+
+ // OSS: invite permission is broadly available; EE: require invite or role-modify rights.
+ const canResetPassword = canInviteMembers || canModifyRoles
if (hidden && !selfMenu) return null
if (!selfMenu && !canInviteMembers && !canModifyRoles) return null
@@ -91,6 +101,24 @@ export const Actions: React.FC<{
}
}
+ const handleResetPassword = async () => {
+ setResetLoading(true)
+ try {
+ const link = await resetPassword(user.id)
+ setGenerateResetLinkOpen(false)
+ setResetLink(typeof link === "string" ? link : String(link))
+ setResetLinkOpen(true)
+ } catch (error: any) {
+ const detail =
+ error?.response?.data?.detail ||
+ error?.message ||
+ "Unable to generate reset password link"
+ message.error(detail)
+ } finally {
+ setResetLoading(false)
+ }
+ }
+
return (
<>
,
+ onClick: (e: any) => {
+ e.domEvent.stopPropagation()
+ setGenerateResetLinkOpen(true)
+ },
+ },
+ ]
+ : []),
...(canModifyRoles
? [
{
@@ -149,7 +190,7 @@ export const Actions: React.FC<{
onClick={(e) => e.stopPropagation()}
type="text"
icon={}
- loading={resendLoading}
+ loading={resendLoading || resetLoading}
/>
@@ -170,6 +211,21 @@ export const Actions: React.FC<{
placeholder="New username"
/>
+
+ setGenerateResetLinkOpen(false)}
+ onOk={handleResetPassword}
+ confirmLoading={resetLoading}
+ />
+
+ setResetLinkOpen(false)}
+ />
>
)
}
diff --git a/web/oss/src/services/profile/index.ts b/web/oss/src/services/profile/index.ts
index 18d6e28488..572aced3a2 100644
--- a/web/oss/src/services/profile/index.ts
+++ b/web/oss/src/services/profile/index.ts
@@ -57,6 +57,19 @@ export const changePassword = async (payload: {
})
}
+/**
+ * Generate a password reset link for a workspace member (admin action).
+ * Calls POST /api/profile/reset-password?user_id=...
+ */
+export const resetPassword = async (userId: string): Promise => {
+ const base = getBaseUrl()
+ const url = new URL("api/profile/reset-password", base)
+ url.searchParams.set("user_id", userId)
+ return fetchJson(url, {
+ method: "POST",
+ })
+}
+
/**
* Permanently delete the current user's account (EE only). Removes the user
* from the database (with the organizations they own), the auth provider,