Skip to content

Commit 280de2a

Browse files
committed
validate-email and reset-password buttons on Manager
1 parent 0ded959 commit 280de2a

7 files changed

Lines changed: 974 additions & 1 deletion

File tree

apps/api-manager/src/lib/utils/roleChecker.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export const SITE_MAP: Record<string, PageRoleConfig> = {
307307
"/users/[provider]/[username]/lock": {
308308
required: [{ role: "CanLockUser" }],
309309
},
310+
"/users/[user_id]/validate-email": {
311+
required: [{ role: "CanGetAnyUser" }],
312+
},
313+
"/users/[user_id]/reset-password": {
314+
required: [{ role: "CanCreateResetPasswordUrl" }],
315+
},
310316

311317
// ── Integration ─────────────────────────────────────
312318
"/integration/method-routings": {

apps/api-manager/src/routes/(protected)/users/[user_id]/+page.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface UserDetail {
1616
last_login_date?: string;
1717
is_locked?: boolean;
1818
is_deleted?: boolean;
19+
email_validated?: boolean;
1920
entitlements?: any;
2021
}
2122

apps/api-manager/src/routes/(protected)/users/[user_id]/+page.svelte

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,16 @@
111111
</div>
112112
<div class="info-item">
113113
<div class="info-label">Email</div>
114-
<div class="info-value">{user.email || "N/A"}</div>
114+
<div class="info-value">
115+
{user.email || "N/A"}
116+
{#if user.email_validated === true && user.user_id}
117+
<a
118+
href="/users/{encodeURIComponent(user.user_id)}/reset-password"
119+
class="action-link"
120+
data-testid="user-send-reset-password-link"
121+
>Send Password Reset Email</a>
122+
{/if}
123+
</div>
115124
</div>
116125
<div class="info-item">
117126
<div class="info-label">Username</div>
@@ -157,6 +166,26 @@
157166
{/if}
158167
</div>
159168
</div>
169+
<div class="info-item">
170+
<div class="info-label">Email Validated</div>
171+
<div class="info-value" data-testid="user-email-validated">
172+
{#if user.email_validated === true}
173+
<span class="badge badge-success">Yes</span>
174+
{:else if user.email_validated === false}
175+
<span class="badge badge-error">No</span>
176+
{#if user.user_id}
177+
<a
178+
href="/users/{encodeURIComponent(user.user_id)}/validate-email"
179+
class="action-link"
180+
style="color: #059669;"
181+
data-testid="user-send-validation-email-link"
182+
>Send Validation Email</a>
183+
{/if}
184+
{:else}
185+
<span class="badge badge-default">Unknown</span>
186+
{/if}
187+
</div>
188+
</div>
160189
{#if user.last_activity_date}
161190
<div class="info-item">
162191
<div class="info-label">Last Activity</div>
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { createLogger } from "@obp/shared/utils";
2+
const logger = createLogger("ResetPasswordPageServer");
3+
import type { PageServerLoad } from "./$types";
4+
import { obp_requests } from "$lib/obp/requests";
5+
import { extractErrorDetails } from "$lib/obp/errors";
6+
import { SessionOAuthHelper } from "$lib/oauth/sessionHelper";
7+
import { error, fail } from "@sveltejs/kit";
8+
import type { RequestEvent } from "@sveltejs/kit";
9+
10+
export const load: PageServerLoad = async ({ locals, params }) => {
11+
const session = locals.session;
12+
13+
if (!session?.data?.user) {
14+
throw error(401, "Unauthorized");
15+
}
16+
17+
const { user_id } = params;
18+
19+
if (!user_id) {
20+
throw error(400, "User ID is required");
21+
}
22+
23+
const sessionOAuth = SessionOAuthHelper.getSessionOAuth(session);
24+
const accessToken = sessionOAuth?.accessToken;
25+
26+
if (!accessToken) {
27+
logger.warn("No access token available for reset-password page");
28+
return {
29+
user: null,
30+
user_id,
31+
hasApiAccess: false,
32+
error: "No API access token available",
33+
};
34+
}
35+
36+
let user = null;
37+
try {
38+
const endpoint = `/obp/v6.0.0/users/user-id/${encodeURIComponent(user_id)}`;
39+
user = await obp_requests.get(endpoint, accessToken);
40+
} catch (err) {
41+
logger.error(
42+
`Error fetching user details: ${err instanceof Error ? err.message : String(err)}`,
43+
);
44+
return {
45+
user: null,
46+
user_id,
47+
hasApiAccess: false,
48+
error: err instanceof Error ? err.message : "Failed to load user details",
49+
};
50+
}
51+
52+
return {
53+
user,
54+
user_id,
55+
hasApiAccess: true,
56+
};
57+
};
58+
59+
export const actions = {
60+
sendResetPasswordEmail: async (event: RequestEvent) => {
61+
const session = event.locals.session;
62+
63+
if (!session?.data?.user) {
64+
return fail(401, { error: "Unauthorized", action: "sendResetPasswordEmail" });
65+
}
66+
67+
const sessionOAuth = SessionOAuthHelper.getSessionOAuth(session);
68+
const token = sessionOAuth?.accessToken;
69+
70+
if (!token) {
71+
return fail(401, {
72+
error: "Unauthorized: No access token found in session.",
73+
action: "sendResetPasswordEmail",
74+
});
75+
}
76+
77+
const { user_id } = event.params;
78+
79+
if (!user_id) {
80+
return fail(400, { error: "User ID is required.", action: "sendResetPasswordEmail" });
81+
}
82+
83+
let user;
84+
try {
85+
const endpoint = `/obp/v6.0.0/users/user-id/${encodeURIComponent(user_id)}`;
86+
user = await obp_requests.get(endpoint, token);
87+
} catch (e: any) {
88+
logger.error("Error fetching user for password reset:", e);
89+
const { message } = extractErrorDetails(e);
90+
return fail(500, { error: message, action: "sendResetPasswordEmail" });
91+
}
92+
93+
if (!user?.user_id || !user?.username || !user?.email) {
94+
return fail(400, {
95+
error: "Could not determine user_id, username and email for this user.",
96+
action: "sendResetPasswordEmail",
97+
});
98+
}
99+
100+
try {
101+
const endpoint = `/obp/v6.0.0/management/user/reset-password-url`;
102+
const result = await obp_requests.post(
103+
endpoint,
104+
{
105+
user_id: user.user_id,
106+
username: user.username,
107+
email: user.email,
108+
},
109+
token,
110+
);
111+
112+
logger.info(
113+
`Password reset email requested for ${user.username} <${user.email}>`,
114+
);
115+
116+
return {
117+
success: true,
118+
action: "sendResetPasswordEmail",
119+
sentTo: result.to,
120+
username: user.username,
121+
apiStatus: result.status,
122+
};
123+
} catch (e: any) {
124+
logger.error("Error requesting password reset email:", e);
125+
const { message } = extractErrorDetails(e);
126+
return fail(500, { error: message, action: "sendResetPasswordEmail" });
127+
}
128+
},
129+
};

0 commit comments

Comments
 (0)