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`); }