diff --git a/components/dashboard/src/admin/UserDetail.tsx b/components/dashboard/src/admin/UserDetail.tsx
index e764f110919f79..fca319f038f78b 100644
--- a/components/dashboard/src/admin/UserDetail.tsx
+++ b/components/dashboard/src/admin/UserDetail.tsx
@@ -75,6 +75,12 @@ export default function UserDetail(p: { user: User }) {
});
};
+ const verifyUser = async () => {
+ await updateUser(async (u) => {
+ return await getGitpodService().server.adminVerifyUser(u.id);
+ });
+ };
+
const toggleBlockUser = async () => {
await updateUser(async (u) => {
u.blocked = !u.blocked;
@@ -132,6 +138,11 @@ export default function UserDetail(p: { user: User }) {
.join(", ")}
+ {!user.lastVerificationTime ? (
+
+ ) : null}
diff --git a/components/gitpod-protocol/src/admin-protocol.ts b/components/gitpod-protocol/src/admin-protocol.ts
index f2f74289181ab2..ded57da5e37a9e 100644
--- a/components/gitpod-protocol/src/admin-protocol.ts
+++ b/components/gitpod-protocol/src/admin-protocol.ts
@@ -18,6 +18,7 @@ export interface AdminServer {
adminGetUser(id: string): Promise;
adminBlockUser(req: AdminBlockUserRequest): Promise;
adminDeleteUser(id: string): Promise;
+ adminVerifyUser(id: string): Promise;
adminModifyRoleOrPermission(req: AdminModifyRoleOrPermissionRequest): Promise;
adminModifyPermanentWorkspaceFeatureFlag(req: AdminModifyPermanentWorkspaceFeatureFlagRequest): Promise;
diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts
index 301bb7ba413899..4075b05161e709 100644
--- a/components/server/ee/src/workspace/gitpod-server-impl.ts
+++ b/components/server/ee/src/workspace/gitpod-server-impl.ts
@@ -643,6 +643,23 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
return this.censorUser(targetUser);
}
+ async adminVerifyUser(ctx: TraceContext, userId: string): Promise {
+ await this.requireEELicense(Feature.FeatureAdminDashboard);
+
+ await this.guardAdminAccess("adminVerifyUser", { id: userId }, Permission.ADMIN_USERS);
+ try {
+ const user = await this.userDB.findUserById(userId);
+ if (!user) {
+ throw new ResponseError(ErrorCodes.NOT_FOUND, `No user with id ${userId} found.`);
+ }
+ this.verificationService.markVerified(user);
+ await this.userDB.updateUserPartial(user);
+ return user;
+ } catch (e) {
+ throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, e.toString());
+ }
+ }
+
async adminDeleteUser(ctx: TraceContext, userId: string): Promise {
traceAPIParams(ctx, { userId });
diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts
index e041213b794017..bdb405c17c0681 100644
--- a/components/server/src/auth/rate-limiter.ts
+++ b/components/server/src/auth/rate-limiter.ts
@@ -150,6 +150,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
adminGetUser: { group: "default", points: 1 },
adminBlockUser: { group: "default", points: 1 },
adminDeleteUser: { group: "default", points: 1 },
+ adminVerifyUser: { group: "default", points: 1 },
adminModifyRoleOrPermission: { group: "default", points: 1 },
adminModifyPermanentWorkspaceFeatureFlag: { group: "default", points: 1 },
adminGetTeams: { group: "default", points: 1 },
diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts
index 5564a540582d5d..4b2e1f68998376 100644
--- a/components/server/src/workspace/gitpod-server-impl.ts
+++ b/components/server/src/workspace/gitpod-server-impl.ts
@@ -2695,6 +2695,10 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
}
+ async adminVerifyUser(ctx: TraceContext, _id: string): Promise {
+ throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
+ }
+
async adminModifyRoleOrPermission(ctx: TraceContext, req: AdminModifyRoleOrPermissionRequest): Promise {
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
}