feat(core/admin): server admin can mark user email as being verified (#5482)
This commit is contained in:
@@ -151,6 +151,12 @@ export type AdminMutations = {
|
||||
__typename?: 'AdminMutations';
|
||||
giveAccessToWorkspaceFeature: Scalars['Boolean']['output'];
|
||||
removeAccessToWorkspaceFeature: Scalars['Boolean']['output'];
|
||||
/**
|
||||
* A server administrator can update the verification status of an user's email.
|
||||
* The server administrator is recommended to confirm ownership of the email address
|
||||
* with the user before performing this action.
|
||||
*/
|
||||
updateEmailVerification: Scalars['Boolean']['output'];
|
||||
updateWorkspacePlan: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
@@ -165,6 +171,11 @@ export type AdminMutationsRemoveAccessToWorkspaceFeatureArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AdminMutationsUpdateEmailVerificationArgs = {
|
||||
input: AdminUpdateEmailVerificationInput;
|
||||
};
|
||||
|
||||
|
||||
export type AdminMutationsUpdateWorkspacePlanArgs = {
|
||||
input: AdminUpdateWorkspacePlanInput;
|
||||
};
|
||||
@@ -209,6 +220,12 @@ export type AdminQueriesWorkspaceListArgs = {
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUpdateEmailVerificationInput = {
|
||||
email: Scalars['String']['input'];
|
||||
/** Defaults to true. If set to false, the email will be marked as unverified. */
|
||||
verified?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUpdateWorkspacePlanInput = {
|
||||
plan: WorkspacePlans;
|
||||
status: WorkspacePlanStatuses;
|
||||
@@ -9306,6 +9323,7 @@ export type AdminInviteListFieldArgs = {
|
||||
export type AdminMutationsFieldArgs = {
|
||||
giveAccessToWorkspaceFeature: AdminMutationsGiveAccessToWorkspaceFeatureArgs,
|
||||
removeAccessToWorkspaceFeature: AdminMutationsRemoveAccessToWorkspaceFeatureArgs,
|
||||
updateEmailVerification: AdminMutationsUpdateEmailVerificationArgs,
|
||||
updateWorkspacePlan: AdminMutationsUpdateWorkspacePlanArgs,
|
||||
}
|
||||
export type AdminQueriesFieldArgs = {
|
||||
|
||||
@@ -32,6 +32,14 @@ type ProjectCollection {
|
||||
items: [Project!]!
|
||||
}
|
||||
|
||||
input AdminUpdateEmailVerificationInput {
|
||||
email: String!
|
||||
"""
|
||||
Defaults to true. If set to false, the email will be marked as unverified.
|
||||
"""
|
||||
verified: Boolean
|
||||
}
|
||||
|
||||
type AdminQueries {
|
||||
userList(
|
||||
limit: Int! = 25
|
||||
@@ -57,6 +65,19 @@ type AdminQueries {
|
||||
serverStatistics: ServerStatistics! @hasScope(scope: "server:stats")
|
||||
}
|
||||
|
||||
type AdminMutations {
|
||||
"""
|
||||
A server administrator can update the verification status of an user's email.
|
||||
The server administrator is recommended to confirm ownership of the email address
|
||||
with the user before performing this action.
|
||||
"""
|
||||
updateEmailVerification(input: AdminUpdateEmailVerificationInput!): Boolean!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
admin: AdminQueries! @hasServerRole(role: SERVER_ADMIN)
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
admin: AdminMutations! @hasServerRole(role: SERVER_ADMIN)
|
||||
}
|
||||
|
||||
@@ -679,10 +679,6 @@ extend type Subscription {
|
||||
@hasScope(scope: "workspace:read")
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
admin: AdminMutations! @hasServerRole(role: SERVER_ADMIN)
|
||||
}
|
||||
|
||||
input AdminUpdateWorkspacePlanInput {
|
||||
workspaceId: ID!
|
||||
plan: WorkspacePlans!
|
||||
@@ -700,7 +696,7 @@ input AdminAccessToWorkspaceFeatureInput {
|
||||
featureFlagName: WorkspaceFeatureFlagName!
|
||||
}
|
||||
|
||||
type AdminMutations {
|
||||
extend type AdminMutations {
|
||||
updateWorkspacePlan(input: AdminUpdateWorkspacePlanInput!): Boolean!
|
||||
giveAccessToWorkspaceFeature(input: AdminAccessToWorkspaceFeatureInput!): Boolean!
|
||||
removeAccessToWorkspaceFeature(input: AdminAccessToWorkspaceFeatureInput!): Boolean!
|
||||
|
||||
@@ -87,6 +87,16 @@ export type UpdateUserServerRole = (params: {
|
||||
role: ServerRoles
|
||||
}) => Promise<boolean>
|
||||
|
||||
export type AdminUpdateEmailVerification = (args: {
|
||||
email: string
|
||||
verified?: MaybeNullOrUndefined<boolean>
|
||||
}) => Promise<boolean>
|
||||
|
||||
export type UpdateUserEmailVerification = (params: {
|
||||
email: string
|
||||
verified: boolean
|
||||
}) => Promise<boolean>
|
||||
|
||||
export type MarkUserAsVerified = (email: string) => Promise<boolean>
|
||||
|
||||
export type MarkOnboardingComplete = (userId: string) => Promise<boolean>
|
||||
|
||||
@@ -173,6 +173,12 @@ export type AdminMutations = {
|
||||
__typename?: 'AdminMutations';
|
||||
giveAccessToWorkspaceFeature: Scalars['Boolean']['output'];
|
||||
removeAccessToWorkspaceFeature: Scalars['Boolean']['output'];
|
||||
/**
|
||||
* A server administrator can update the verification status of an user's email.
|
||||
* The server administrator is recommended to confirm ownership of the email address
|
||||
* with the user before performing this action.
|
||||
*/
|
||||
updateEmailVerification: Scalars['Boolean']['output'];
|
||||
updateWorkspacePlan: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
@@ -187,6 +193,11 @@ export type AdminMutationsRemoveAccessToWorkspaceFeatureArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AdminMutationsUpdateEmailVerificationArgs = {
|
||||
input: AdminUpdateEmailVerificationInput;
|
||||
};
|
||||
|
||||
|
||||
export type AdminMutationsUpdateWorkspacePlanArgs = {
|
||||
input: AdminUpdateWorkspacePlanInput;
|
||||
};
|
||||
@@ -231,6 +242,12 @@ export type AdminQueriesWorkspaceListArgs = {
|
||||
query?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUpdateEmailVerificationInput = {
|
||||
email: Scalars['String']['input'];
|
||||
/** Defaults to true. If set to false, the email will be marked as unverified. */
|
||||
verified?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type AdminUpdateWorkspacePlanInput = {
|
||||
plan: WorkspacePlans;
|
||||
status: WorkspacePlanStatuses;
|
||||
@@ -6149,6 +6166,7 @@ export type ResolversTypes = {
|
||||
AdminInviteList: ResolverTypeWrapper<Omit<AdminInviteList, 'items'> & { items: Array<ResolversTypes['ServerInvite']> }>;
|
||||
AdminMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
|
||||
AdminQueries: ResolverTypeWrapper<GraphQLEmptyReturn>;
|
||||
AdminUpdateEmailVerificationInput: AdminUpdateEmailVerificationInput;
|
||||
AdminUpdateWorkspacePlanInput: AdminUpdateWorkspacePlanInput;
|
||||
AdminUserList: ResolverTypeWrapper<AdminUserList>;
|
||||
AdminUserListItem: ResolverTypeWrapper<AdminUserListItem>;
|
||||
@@ -6550,6 +6568,7 @@ export type ResolversParentTypes = {
|
||||
AdminInviteList: Omit<AdminInviteList, 'items'> & { items: Array<ResolversParentTypes['ServerInvite']> };
|
||||
AdminMutations: MutationsObjectGraphQLReturn;
|
||||
AdminQueries: GraphQLEmptyReturn;
|
||||
AdminUpdateEmailVerificationInput: AdminUpdateEmailVerificationInput;
|
||||
AdminUpdateWorkspacePlanInput: AdminUpdateWorkspacePlanInput;
|
||||
AdminUserList: AdminUserList;
|
||||
AdminUserListItem: AdminUserListItem;
|
||||
@@ -7009,6 +7028,7 @@ export type AdminInviteListResolvers<ContextType = GraphQLContext, ParentType ex
|
||||
export type AdminMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['AdminMutations'] = ResolversParentTypes['AdminMutations']> = {
|
||||
giveAccessToWorkspaceFeature?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<AdminMutationsGiveAccessToWorkspaceFeatureArgs, 'input'>>;
|
||||
removeAccessToWorkspaceFeature?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<AdminMutationsRemoveAccessToWorkspaceFeatureArgs, 'input'>>;
|
||||
updateEmailVerification?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<AdminMutationsUpdateEmailVerificationArgs, 'input'>>;
|
||||
updateWorkspacePlan?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<AdminMutationsUpdateWorkspacePlanArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
@@ -9438,6 +9458,13 @@ export type SetLegacyProjectsExplainerCollapsedMutationVariables = Exact<{
|
||||
|
||||
export type SetLegacyProjectsExplainerCollapsedMutation = { __typename?: 'Mutation', activeUserMutations: { __typename?: 'ActiveUserMutations', meta: { __typename?: 'UserMetaMutations', setLegacyProjectsExplainerCollapsed: boolean } } };
|
||||
|
||||
export type AdminMutationsMutationVariables = Exact<{
|
||||
input: AdminUpdateEmailVerificationInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type AdminMutationsMutation = { __typename?: 'Mutation', admin: { __typename?: 'AdminMutations', updateEmailVerification: boolean } };
|
||||
|
||||
export type LimitedPersonalProjectCommentFragment = { __typename?: 'Comment', id: string, rawText?: string | null, createdAt: Date, text?: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null, type: string } | null };
|
||||
|
||||
export type LimitedPersonalProjectVersionFragment = { __typename?: 'Version', id: string, createdAt: Date, message?: string | null, referencedObject?: string | null, commentThreads: { __typename?: 'CommentCollection', totalCount: number, items: Array<{ __typename?: 'Comment', id: string, rawText?: string | null, createdAt: Date, text?: { __typename?: 'SmartTextEditorValue', doc?: Record<string, unknown> | null, type: string } | null }> } };
|
||||
@@ -10776,6 +10803,7 @@ export const GetIntelligenceCommunityStandUpBannerDismissedDocument = {"kind":"D
|
||||
export const SetIntelligenceCommunityStandUpBannerDismissedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetIntelligenceCommunityStandUpBannerDismissed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUserMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"meta"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setIntelligenceCommunityStandUpBannerDismissed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]}}]} as unknown as DocumentNode<SetIntelligenceCommunityStandUpBannerDismissedMutation, SetIntelligenceCommunityStandUpBannerDismissedMutationVariables>;
|
||||
export const GetLegacyProjectsExplainerCollapsedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetLegacyProjectsExplainerCollapsed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"meta"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"legacyProjectsExplainerCollapsed"}}]}}]}}]}}]} as unknown as DocumentNode<GetLegacyProjectsExplainerCollapsedQuery, GetLegacyProjectsExplainerCollapsedQueryVariables>;
|
||||
export const SetLegacyProjectsExplainerCollapsedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetLegacyProjectsExplainerCollapsed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUserMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"meta"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setLegacyProjectsExplainerCollapsed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]}}]} as unknown as DocumentNode<SetLegacyProjectsExplainerCollapsedMutation, SetLegacyProjectsExplainerCollapsedMutationVariables>;
|
||||
export const AdminMutationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AdminMutations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AdminUpdateEmailVerificationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"admin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateEmailVerification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<AdminMutationsMutation, AdminMutationsMutationVariables>;
|
||||
export const GetLimitedPersonalProjectVersionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetLimitedPersonalProjectVersions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalProjectVersion"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectComment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"commentThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalProjectComment"}}]}}]}}]}}]} as unknown as DocumentNode<GetLimitedPersonalProjectVersionsQuery, GetLimitedPersonalProjectVersionsQueryVariables>;
|
||||
export const GetLimitedPersonalProjectVersionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetLimitedPersonalProjectVersion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalProjectVersion"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectComment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"commentThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalProjectComment"}}]}}]}}]}}]} as unknown as DocumentNode<GetLimitedPersonalProjectVersionQuery, GetLimitedPersonalProjectVersionQueryVariables>;
|
||||
export const GetLimitedPersonalStreamCommitsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetLimitedPersonalStreamCommits"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalStreamCommit"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalStreamCommit"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Commit"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetLimitedPersonalStreamCommitsQuery, GetLimitedPersonalStreamCommitsQueryVariables>;
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { db as mainDb } from '@/db/knex'
|
||||
import type { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { mapServerRoleToValue } from '@/modules/core/helpers/graphTypes'
|
||||
import { toProjectIdWhitelist } from '@/modules/core/helpers/token'
|
||||
import { legacyGetStreamsFactory } from '@/modules/core/repositories/streams'
|
||||
import { countUsersFactory, listUsersFactory } from '@/modules/core/repositories/users'
|
||||
import {
|
||||
countUsersFactory,
|
||||
listUsersFactory,
|
||||
updateUserEmailVerificationFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
adminUpdateEmailVerificationFactory,
|
||||
adminInviteListFactory,
|
||||
adminProjectListFactory,
|
||||
adminUserListFactory
|
||||
} from '@/modules/core/services/admin'
|
||||
import { deleteVerificationsFactory } from '@/modules/emails/repositories'
|
||||
import {
|
||||
countServerInvitesFactory,
|
||||
queryServerInvitesFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { asMultiregionalOperation } from '@/modules/shared/command'
|
||||
import {
|
||||
getTotalStreamCountFactory,
|
||||
getTotalUserCountFactory
|
||||
} from '@/modules/stats/repositories'
|
||||
import { updateUserEmailFactory } from '@/modules/core/repositories/userEmails'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
|
||||
|
||||
const adminUserList = adminUserListFactory({
|
||||
listUsers: listUsersFactory({ db }),
|
||||
countUsers: countUsersFactory({ db })
|
||||
listUsers: listUsersFactory({ db: mainDb }),
|
||||
countUsers: countUsersFactory({ db: mainDb })
|
||||
})
|
||||
const adminInviteList = adminInviteListFactory({
|
||||
countServerInvites: countServerInvitesFactory({ db }),
|
||||
queryServerInvites: queryServerInvitesFactory({ db })
|
||||
countServerInvites: countServerInvitesFactory({ db: mainDb }),
|
||||
queryServerInvites: queryServerInvitesFactory({ db: mainDb })
|
||||
})
|
||||
const adminProjectList = adminProjectListFactory({
|
||||
getStreams: legacyGetStreamsFactory({ db })
|
||||
getStreams: legacyGetStreamsFactory({ db: mainDb })
|
||||
})
|
||||
|
||||
export default {
|
||||
@@ -58,13 +68,56 @@ export default {
|
||||
return await adminInviteList(args)
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
admin: () => ({})
|
||||
},
|
||||
AdminMutations: {
|
||||
async updateEmailVerification(_parent, args, ctx) {
|
||||
try {
|
||||
return await asMultiregionalOperation(
|
||||
async ({ mainDb, allDbs }) => {
|
||||
const updateEmailVerification = adminUpdateEmailVerificationFactory({
|
||||
deleteVerifications: deleteVerificationsFactory({ db: mainDb }),
|
||||
// this updates the users table
|
||||
updateUserVerification: async (...params) => {
|
||||
const emailVerified = await Promise.all(
|
||||
allDbs.map((db) =>
|
||||
updateUserEmailVerificationFactory({
|
||||
db
|
||||
})(...params)
|
||||
)
|
||||
)
|
||||
return emailVerified.every(Boolean)
|
||||
},
|
||||
// this updates the user_emails table
|
||||
updateEmail: updateUserEmailFactory({ db: mainDb })
|
||||
})
|
||||
|
||||
return await updateEmailVerification({
|
||||
email: args.input.email.toLowerCase().trim(),
|
||||
verified: args.input.verified
|
||||
})
|
||||
},
|
||||
{
|
||||
logger: ctx.log,
|
||||
name: 'adminUpdateEmailVerification',
|
||||
description: 'Email verification updated by a server admin',
|
||||
dbs: await getAllRegisteredDbs()
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
const err = ensureError(e, 'Unknown error while updating email verification')
|
||||
ctx.log.info({ err }, 'Email verification by Admin failed.')
|
||||
}
|
||||
}
|
||||
},
|
||||
ServerStatistics: {
|
||||
async totalProjectCount() {
|
||||
return await getTotalStreamCountFactory({ db })()
|
||||
return await getTotalStreamCountFactory({ db: mainDb })()
|
||||
},
|
||||
|
||||
async totalUserCount() {
|
||||
return await getTotalUserCountFactory({ db })()
|
||||
return await getTotalUserCountFactory({ db: mainDb })()
|
||||
},
|
||||
async totalPendingInvites() {
|
||||
return 0
|
||||
|
||||
@@ -48,6 +48,7 @@ import type {
|
||||
StoreUser,
|
||||
StoreUserAcl,
|
||||
UpdateUser,
|
||||
UpdateUserEmailVerification,
|
||||
UpdateUserServerRole
|
||||
} from '@/modules/core/domain/users/operations'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
@@ -219,14 +220,20 @@ export const getUserByEmailFactory =
|
||||
*/
|
||||
export const markUserAsVerifiedFactory =
|
||||
(deps: { db: Knex }): MarkUserAsVerified =>
|
||||
async (email: string) => {
|
||||
async (email: string) =>
|
||||
updateUserEmailVerificationFactory(deps)({ email, verified: true })
|
||||
|
||||
export const updateUserEmailVerificationFactory =
|
||||
(deps: { db: Knex }): UpdateUserEmailVerification =>
|
||||
async (args) => {
|
||||
const { email, verified } = args
|
||||
const UserCols = Users.with({ withoutTablePrefix: true }).col
|
||||
|
||||
const usersUpdate = await tables
|
||||
.users(deps.db)
|
||||
.whereRaw('lower(email) = lower(?)', [email])
|
||||
.update({
|
||||
[UserCols.verified]: true
|
||||
[UserCols.verified]: verified
|
||||
})
|
||||
|
||||
return !!usersUpdate
|
||||
|
||||
@@ -4,18 +4,22 @@ import type {
|
||||
} from '@/modules/core/domain/streams/operations'
|
||||
import type {
|
||||
AdminGetInviteList,
|
||||
AdminUpdateEmailVerification,
|
||||
AdminUserList,
|
||||
CountUsers,
|
||||
ListPaginatedUsersPage
|
||||
ListPaginatedUsersPage,
|
||||
UpdateUserEmailVerification
|
||||
} from '@/modules/core/domain/users/operations'
|
||||
import type { ProjectRecordVisibility } from '@/modules/core/helpers/types'
|
||||
import type { DeleteVerifications } from '@/modules/emails/domain/operations'
|
||||
import type {
|
||||
CountServerInvites,
|
||||
QueryServerInvites
|
||||
} from '@/modules/serverinvites/domain/operations'
|
||||
import type { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
||||
import { BaseError } from '@/modules/shared/errors/base'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
import { type Nullable } from '@speckle/shared'
|
||||
import type { UpdateUserEmail } from '@/modules/core/domain/userEmails/operations'
|
||||
|
||||
class CursorParsingError extends BaseError {
|
||||
static defaultMessage = 'Invalid cursor provided'
|
||||
@@ -103,3 +107,36 @@ export const adminProjectListFactory =
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
|
||||
export const adminUpdateEmailVerificationFactory =
|
||||
(deps: {
|
||||
deleteVerifications: DeleteVerifications
|
||||
updateUserVerification: UpdateUserEmailVerification
|
||||
updateEmail: UpdateUserEmail
|
||||
}): AdminUpdateEmailVerification =>
|
||||
async (args) => {
|
||||
const { email } = args
|
||||
let { verified } = args
|
||||
if (verified === undefined || verified === null) {
|
||||
verified = true
|
||||
}
|
||||
|
||||
if (verified) {
|
||||
await deps.deleteVerifications(email)
|
||||
}
|
||||
|
||||
const result = await Promise.all([
|
||||
// this updates the 'users' table
|
||||
deps.updateUserVerification({
|
||||
email,
|
||||
verified
|
||||
}),
|
||||
// this updates the 'user_emails' table
|
||||
deps.updateEmail({
|
||||
query: { email },
|
||||
update: { verified }
|
||||
})
|
||||
])
|
||||
|
||||
return result[0] && !!result[1]?.verified
|
||||
}
|
||||
|
||||
@@ -269,3 +269,11 @@ export const setLegacyProjectsExplainerCollapsedMutation = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const updateEmailVerificationMutation = gql`
|
||||
mutation AdminMutations($input: AdminUpdateEmailVerificationInput!) {
|
||||
admin {
|
||||
updateEmailVerification(input: $input)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import type { ExecuteOperationResponse, TestApolloServer } from '@/test/graphqlHelper'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { expect } from 'chai'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { AdminMutationsDocument } from '@/modules/core/graph/generated/graphql'
|
||||
import { createRandomEmail } from '@/modules/core/helpers/testHelpers'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const testForbiddenResponse = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
result: ExecuteOperationResponse<Record<string, any>>
|
||||
) => {
|
||||
expect(result.errors, 'This should have failed').to.exist
|
||||
expect(result.errors!.length).to.be.above(0)
|
||||
expect(result.errors![0].extensions!.code, JSON.stringify(result.errors)).to.match(
|
||||
/(STREAM_INVALID_ACCESS_ERROR|FORBIDDEN|UNAUTHORIZED_ACCESS_ERROR)/
|
||||
)
|
||||
}
|
||||
|
||||
const getActiveUserVerifiedQuery = gql`
|
||||
query GetActiveUser {
|
||||
activeUser {
|
||||
verified
|
||||
emails {
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('Admin @core-admin Graphql', () => {
|
||||
const serverAdminUser: BasicTestUser = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'I am Admin',
|
||||
role: Roles.Server.Admin
|
||||
}
|
||||
|
||||
const regularServerUser = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'regular server user',
|
||||
role: Roles.Server.User
|
||||
}
|
||||
const archivedUser = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'archived user',
|
||||
role: Roles.Server.ArchivedUser
|
||||
}
|
||||
const unaffiliatedUser = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'unaffiliated user',
|
||||
role: Roles.Server.Guest
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
await createTestUser(serverAdminUser)
|
||||
await createTestUser(regularServerUser)
|
||||
await createTestUser(archivedUser)
|
||||
await createTestUser(unaffiliatedUser)
|
||||
})
|
||||
|
||||
describe('updateEmailVerification', () => {
|
||||
describe('when attempting to verify another users email', () => {
|
||||
const testCases = [
|
||||
{ user: serverAdminUser, canVerify: true },
|
||||
{ user: regularServerUser, canVerify: false },
|
||||
{ user: archivedUser, canVerify: false },
|
||||
{ user: unaffiliatedUser, canVerify: false }
|
||||
]
|
||||
|
||||
testCases.forEach(({ user: testUser, canVerify }) => {
|
||||
let apollo: TestApolloServer
|
||||
before(async () => {
|
||||
// we are purposefully not using the helper to create the token, as we want to test with multiple users
|
||||
apollo = await testApolloServer()
|
||||
})
|
||||
|
||||
it(`a ${testUser.role} is ${canVerify ? 'allowed' : 'forbidden'}`, async () => {
|
||||
const userToVerify = {
|
||||
id: '',
|
||||
email: createRandomEmail(),
|
||||
name: 'unverified user',
|
||||
role: Roles.Server.User,
|
||||
verified: false
|
||||
}
|
||||
await createTestUser(userToVerify)
|
||||
|
||||
const preCheckRes = await apollo.execute(
|
||||
getActiveUserVerifiedQuery,
|
||||
{},
|
||||
{
|
||||
authUserId: userToVerify.id,
|
||||
assertNoErrors: true
|
||||
}
|
||||
)
|
||||
expect(preCheckRes.data?.activeUser).to.exist
|
||||
expect(preCheckRes.data?.activeUser?.verified).to.equal(false)
|
||||
expect(
|
||||
preCheckRes.data?.activeUser.emails.some(
|
||||
(email: { verified: boolean }) => email.verified
|
||||
)
|
||||
).to.be.false
|
||||
|
||||
const verifyRes = await apollo.execute(
|
||||
AdminMutationsDocument,
|
||||
{
|
||||
input: { email: userToVerify.email }
|
||||
},
|
||||
{
|
||||
authUserId: testUser.id // auth as the test user
|
||||
}
|
||||
)
|
||||
if (!canVerify) {
|
||||
testForbiddenResponse(verifyRes)
|
||||
return
|
||||
}
|
||||
|
||||
expect(verifyRes).to.not.haveGraphQLErrors()
|
||||
expect(verifyRes.data?.admin.updateEmailVerification).to.equal(canVerify)
|
||||
|
||||
const postCheckRes = await apollo.execute(
|
||||
getActiveUserVerifiedQuery,
|
||||
{},
|
||||
{
|
||||
authUserId: userToVerify.id,
|
||||
assertNoErrors: true
|
||||
}
|
||||
)
|
||||
expect(postCheckRes.data?.activeUser.verified).to.equal(canVerify)
|
||||
expect(postCheckRes.data?.activeUser.emails).to.have.length(1)
|
||||
expect(
|
||||
postCheckRes.data?.activeUser.emails.every(
|
||||
(email: { verified: boolean }) => email.verified
|
||||
)
|
||||
).to.equal(canVerify)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
findEmailFactory,
|
||||
updateUserEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import { expect } from 'chai'
|
||||
import { createTestUser } from '@/test/authHelper'
|
||||
import { adminUpdateEmailVerificationFactory } from '@/modules/core/services/admin'
|
||||
import {
|
||||
getUserFactory,
|
||||
updateUserEmailVerificationFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import {
|
||||
deleteVerificationsFactory,
|
||||
getPendingVerificationByEmailFactory
|
||||
} from '@/modules/emails/repositories'
|
||||
|
||||
describe('Admin @core-admin', () => {
|
||||
it('can mark users as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
|
||||
const testUser = await createTestUser({
|
||||
name: 'John',
|
||||
email,
|
||||
password: createRandomPassword(),
|
||||
verified: false
|
||||
})
|
||||
|
||||
await adminUpdateEmailVerificationFactory({
|
||||
updateEmail: updateUserEmailFactory({ db }),
|
||||
deleteVerifications: deleteVerificationsFactory({ db }),
|
||||
updateUserVerification: updateUserEmailVerificationFactory({ db })
|
||||
})({
|
||||
email
|
||||
//verified: true // defaults to true
|
||||
})
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email })
|
||||
expect(userEmail).to.be.ok
|
||||
expect(userEmail!.verified).to.be.true
|
||||
|
||||
const pendingVerifications = await getPendingVerificationByEmailFactory({
|
||||
db,
|
||||
verificationTimeoutMinutes: 100 // minutes; we don't care for this test
|
||||
})({ email })
|
||||
expect(pendingVerifications).to.be.undefined
|
||||
|
||||
const user = await getUserFactory({ db })(testUser.id)
|
||||
expect(user).to.be.ok
|
||||
expect(user!.verified).to.be.true
|
||||
})
|
||||
|
||||
it('idempotent when marking already verified users as verified', async () => {
|
||||
const email = createRandomEmail()
|
||||
|
||||
const testUser = await createTestUser({
|
||||
name: 'John',
|
||||
email,
|
||||
password: createRandomPassword(),
|
||||
verified: true
|
||||
})
|
||||
|
||||
await adminUpdateEmailVerificationFactory({
|
||||
updateEmail: updateUserEmailFactory({ db }),
|
||||
deleteVerifications: deleteVerificationsFactory({ db }),
|
||||
updateUserVerification: updateUserEmailVerificationFactory({ db })
|
||||
})({
|
||||
email,
|
||||
verified: true
|
||||
})
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email })
|
||||
expect(userEmail).to.be.ok
|
||||
expect(userEmail!.verified).to.be.true
|
||||
|
||||
const pendingVerifications = await getPendingVerificationByEmailFactory({
|
||||
db,
|
||||
verificationTimeoutMinutes: 100 // minutes
|
||||
})({ email })
|
||||
expect(pendingVerifications).to.be.undefined
|
||||
|
||||
const user = await getUserFactory({ db })(testUser.id)
|
||||
expect(user).to.be.ok
|
||||
expect(user!.verified).to.be.true
|
||||
})
|
||||
it('can mark verified users as unverified', async () => {
|
||||
const email = createRandomEmail()
|
||||
|
||||
const testUser = await createTestUser({
|
||||
name: 'John',
|
||||
email,
|
||||
password: createRandomPassword(),
|
||||
verified: true
|
||||
})
|
||||
|
||||
await adminUpdateEmailVerificationFactory({
|
||||
updateEmail: updateUserEmailFactory({ db }),
|
||||
deleteVerifications: deleteVerificationsFactory({ db }),
|
||||
updateUserVerification: updateUserEmailVerificationFactory({ db })
|
||||
})({
|
||||
email,
|
||||
verified: false
|
||||
})
|
||||
|
||||
const userEmail = await findEmailFactory({ db })({ email })
|
||||
expect(userEmail).to.be.ok
|
||||
expect(userEmail!.verified).to.be.false
|
||||
|
||||
const pendingVerifications = await getPendingVerificationByEmailFactory({
|
||||
db,
|
||||
verificationTimeoutMinutes: 100 // minutes
|
||||
})({ email })
|
||||
expect(pendingVerifications).to.be.undefined
|
||||
|
||||
const user = await getUserFactory({ db })(testUser.id)
|
||||
expect(user).to.be.ok
|
||||
expect(user!.verified).to.be.false
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user