Maintenance: Do not establish a session when verifying a signup email address.
Approved-by: Dusan Vuckovic <dv@zammad.com> Co-authored-by: Mantas <mantas@idev.lt>
This commit is contained in:
committed by
Florian Liebe
parent
9a1439e7fc
commit
1e486439ef
@@ -153,6 +153,32 @@ requires `admin.organization`, and agent tokens receive `403 Forbidden`.
|
||||
|
||||
## 7.2
|
||||
|
||||
### Verifying a signup email address no longer signs the user in
|
||||
|
||||
**Who is affected?** Integrations that redeem a signup verification token through
|
||||
`POST /api/v1/users/email_verify` or the `userSignupVerify` GraphQL mutation and rely on the session
|
||||
that the redemption used to establish, and admins who send a verification or password reset link to a
|
||||
deactivated account.
|
||||
|
||||
Redeeming the token established a fully authenticated session for the account, without the account's
|
||||
password and without the second factor configured for it. Both endpoints now only mark the email
|
||||
address as verified and establish no session. The REST endpoint still answers
|
||||
`200 {"message":"ok","user_email":"…"}`, but the response no longer carries a session cookie. The
|
||||
mutation's payload field `session` was replaced by a boolean `success`, so a client selecting
|
||||
`session { id }` receives a GraphQL error.
|
||||
|
||||
A non-persistent token — `Signup`, `PasswordReset`, `AdminAuth`, `KnowledgeBasePreview` — is now
|
||||
rejected when it belongs to an inactive user. Previously only persistent tokens were checked this
|
||||
way. The mail carrying such a link is still sent, but the link itself no longer works while the
|
||||
account is deactivated.
|
||||
|
||||
⚠️ Sign in through `POST /api/v1/signin` or the `login` mutation after redeeming a verification
|
||||
token, and pass the configured second factor there. Change GraphQL clients to select `success`
|
||||
instead of `session`. Activate a deactivated account before sending it a verification or password
|
||||
reset link.
|
||||
|
||||
**Related advisory:** [GHSA-f3qr-94c2-7mx2](https://github.com/zammad/zammad/security/advisories/GHSA-f3qr-94c2-7mx2)
|
||||
|
||||
### The getting started endpoints require `admin.wizard` once the system is set up
|
||||
|
||||
**Who is affected?** Integrations that read `GET /api/v1/getting_started` or
|
||||
|
||||
@@ -35,7 +35,7 @@ class Success extends App.ControllerAppContent
|
||||
detail: __('Woo hoo! Your email address has been verified!')
|
||||
)
|
||||
delay = =>
|
||||
App.Auth.loginCheck(=> @navigate '#')
|
||||
@navigate '#login'
|
||||
@delay(delay, 2000)
|
||||
|
||||
class Fail extends App.ControllerAppContent
|
||||
|
||||
@@ -287,8 +287,6 @@ curl http://localhost/api/v1/users/email_verify -v -u #{login}:#{password} -H "C
|
||||
|
||||
user = Service::User::SignupVerify.with_current_user(false).execute(token: params[:token])
|
||||
|
||||
current_user_set(user) if user
|
||||
|
||||
msg = user ? { message: 'ok', user_email: user.email } : { message: 'failed' }
|
||||
|
||||
render json: msg, status: :ok
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('signup verify view', () => {
|
||||
it('shows an error message when an invalid token is supplied', async () => {
|
||||
mockUserSignupVerifyMutation({
|
||||
userSignupVerify: {
|
||||
session: null,
|
||||
success: null,
|
||||
errors: [{ message: 'The provided token is invalid.' }],
|
||||
},
|
||||
})
|
||||
@@ -66,14 +66,29 @@ describe('signup verify view', () => {
|
||||
})
|
||||
|
||||
it('shows a success message when a valid token is supplied', async () => {
|
||||
mockUserSignupVerifyMutation({
|
||||
userSignupVerify: {
|
||||
success: true,
|
||||
errors: null,
|
||||
},
|
||||
})
|
||||
|
||||
const view = await visitView('/signup/verify/123')
|
||||
|
||||
expect(
|
||||
await view.findByText('Woo hoo! Your email address has been verified!'),
|
||||
).toBeInTheDocument()
|
||||
expect(view.getByText('Please sign in to continue.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('redirects to dashboard screen when the verification was successful', async () => {
|
||||
it('redirects to the login screen when the verification was successful', async () => {
|
||||
mockUserSignupVerifyMutation({
|
||||
userSignupVerify: {
|
||||
success: true,
|
||||
errors: null,
|
||||
},
|
||||
})
|
||||
|
||||
vi.useFakeTimers()
|
||||
|
||||
await visitView('/signup/verify/123')
|
||||
@@ -84,7 +99,7 @@ describe('signup verify view', () => {
|
||||
await waitFor(() => {
|
||||
const router = getTestRouter()
|
||||
const route = router.currentRoute.value
|
||||
expect(route.name).toBe('Dashboard')
|
||||
expect(route.name).toBe('Login')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as Types from '#shared/graphql/types.ts';
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
import { SessionFragmentDoc } from '../../../../../../shared/graphql/fragments/session.api';
|
||||
import { ErrorsFragmentDoc } from '../../../../../../shared/graphql/fragments/errors.api';
|
||||
import * as VueApolloComposable from '@vue/apollo-composable';
|
||||
import * as VueCompositionApi from 'vue';
|
||||
@@ -10,16 +9,13 @@ export type ReactiveFunction<TParam> = () => TParam;
|
||||
export const UserSignupVerifyDocument = gql`
|
||||
mutation userSignupVerify($token: String!) {
|
||||
userSignupVerify(token: $token) {
|
||||
session {
|
||||
...session
|
||||
}
|
||||
success
|
||||
errors {
|
||||
...errors
|
||||
}
|
||||
}
|
||||
}
|
||||
${SessionFragmentDoc}
|
||||
${ErrorsFragmentDoc}`;
|
||||
${ErrorsFragmentDoc}`;
|
||||
export function useUserSignupVerifyMutation(options: VueApolloComposable.UseMutationOptions<Types.UserSignupVerifyMutation, Types.UserSignupVerifyMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<Types.UserSignupVerifyMutation, Types.UserSignupVerifyMutationVariables>> = {}) {
|
||||
return VueApolloComposable.useMutation<Types.UserSignupVerifyMutation, Types.UserSignupVerifyMutationVariables>(UserSignupVerifyDocument, options);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
mutation userSignupVerify($token: String!) {
|
||||
userSignupVerify(token: $token) {
|
||||
session {
|
||||
...session
|
||||
}
|
||||
success
|
||||
errors {
|
||||
...errors
|
||||
}
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import useFingerprint from '#shared/composables/useFingerprint.ts'
|
||||
import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
|
||||
import { useApplicationStore } from '#shared/stores/application.ts'
|
||||
import { useAuthenticationStore } from '#shared/stores/authentication.ts'
|
||||
|
||||
import CommonLoader from '#desktop/components/CommonLoader/CommonLoader.vue'
|
||||
import LayoutPublicPage from '#desktop/components/layout/LayoutPublicPage/LayoutPublicPage.vue'
|
||||
|
||||
import { ensureAfterAuth } from '../after-auth/composable/useAfterAuthPlugins.ts'
|
||||
import { useUserSignupVerifyMutation } from '../graphql/mutations/userSignupVerify.api.ts'
|
||||
|
||||
import type { VerifyState } from '../types/signup.ts'
|
||||
@@ -59,16 +56,9 @@ onMounted(() => {
|
||||
return
|
||||
}
|
||||
|
||||
const { fingerprint } = useFingerprint()
|
||||
|
||||
const userSignupVerify = new MutationHandler(
|
||||
useUserSignupVerifyMutation({
|
||||
variables: { token: props.token },
|
||||
context: {
|
||||
headers: {
|
||||
'X-Browser-Fingerprint': fingerprint.value,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
errorShowNotification: false,
|
||||
@@ -77,28 +67,18 @@ onMounted(() => {
|
||||
|
||||
userSignupVerify
|
||||
.send()
|
||||
.then(async (result) => {
|
||||
const { setAuthenticatedSessionId } = useAuthenticationStore()
|
||||
|
||||
if (await setAuthenticatedSessionId(result?.userSignupVerify?.session?.id || null)) {
|
||||
setState('success')
|
||||
|
||||
const afterAuth = result?.userSignupVerify?.session?.afterAuth
|
||||
|
||||
// Redirect only after some seconds, in order to give the user a chance to read the message.
|
||||
window.setTimeout(() => {
|
||||
if (afterAuth) {
|
||||
ensureAfterAuth(router, afterAuth)
|
||||
return
|
||||
}
|
||||
|
||||
router.replace('/')
|
||||
}, 2000)
|
||||
|
||||
.then((result) => {
|
||||
if (!result?.userSignupVerify?.success) {
|
||||
setState('error')
|
||||
return
|
||||
}
|
||||
|
||||
setState('error')
|
||||
setState('success')
|
||||
|
||||
// Redirect only after some seconds, in order to give the user a chance to read the message.
|
||||
window.setTimeout(() => {
|
||||
router.replace('/login')
|
||||
}, 2000)
|
||||
})
|
||||
.catch(() => {
|
||||
setState('error')
|
||||
@@ -112,6 +92,9 @@ onMounted(() => {
|
||||
<CommonLabel>
|
||||
{{ $t(message) }}
|
||||
</CommonLabel>
|
||||
<CommonLabel v-if="state === 'success'" class="mt-1 block">
|
||||
{{ $t('Please sign in to continue.') }}
|
||||
</CommonLabel>
|
||||
<CommonLoader v-if="state === 'loading'" loading class="mt-9 mb-3" />
|
||||
<CommonIcon
|
||||
v-else-if="state === 'success'"
|
||||
|
||||
@@ -7395,8 +7395,8 @@ export type UserSignupVerifyPayload = {
|
||||
__typename?: 'UserSignupVerifyPayload';
|
||||
/** Errors encountered during execution of the mutation. */
|
||||
errors?: Maybe<Array<UserError>>;
|
||||
/** The current session, if the verification was successful. */
|
||||
session?: Maybe<Session>;
|
||||
/** This indicates if the verification was successful. */
|
||||
success?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
/** Users taskbar item */
|
||||
|
||||
@@ -906,7 +906,7 @@ export type UserSignupVerifyMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UserSignupVerifyMutation = { userSignupVerify: { __typename: 'UserSignupVerifyPayload', session: { __typename: 'Session', id: string, afterAuth: { __typename: 'SessionAfterAuth', type: Types.EnumAfterAuthType, data: any } | null | undefined } | null | undefined, errors: Array<{ __typename: 'UserError', message: string, messagePlaceholder: Array<string> | null | undefined, field: string | null | undefined, exception: Types.EnumUserErrorException | null | undefined }> | null | undefined } | null | undefined };
|
||||
export type UserSignupVerifyMutation = { userSignupVerify: { __typename: 'UserSignupVerifyPayload', success: boolean | null | undefined, errors: Array<{ __typename: 'UserError', message: string, messagePlaceholder: Array<string> | null | undefined, field: string | null | undefined, exception: Types.EnumUserErrorException | null | undefined }> | null | undefined } | null | undefined };
|
||||
|
||||
export type CtiLogAttributesFragment = { __typename: 'CtiLog', id: string, direction: string, state: string, comment: string | null | undefined, from: string, to: string, fromPretty: string, toPretty: string, fromComment: string | null | undefined, toComment: string | null | undefined, done: boolean, durationWaitingTime: number | null | undefined, durationTalkingTime: number | null | undefined, createdAt: string, fromMatches: Array<{ __typename: 'CtiLogCallerIdMatch', level: string, comment: string | null | undefined, user: { __typename: 'User', id: string, internalId: number, firstname: string | null | undefined, lastname: string | null | undefined, fullname: string | null | undefined, image: string | null | undefined, vip: boolean | null | undefined, outOfOffice: boolean | null | undefined, outOfOfficeStartAt: string | null | undefined, outOfOfficeEndAt: string | null | undefined, active: boolean | null | undefined } | null | undefined }>, toMatches: Array<{ __typename: 'CtiLogCallerIdMatch', level: string, comment: string | null | undefined, user: { __typename: 'User', id: string, internalId: number, firstname: string | null | undefined, lastname: string | null | undefined, fullname: string | null | undefined, image: string | null | undefined, vip: boolean | null | undefined, outOfOffice: boolean | null | undefined, outOfOfficeStartAt: string | null | undefined, outOfOfficeEndAt: string | null | undefined, active: boolean | null | undefined } | null | undefined }> };
|
||||
|
||||
|
||||
@@ -2,22 +2,18 @@
|
||||
|
||||
module Gql::Mutations
|
||||
class User::SignupVerify < BaseMutation
|
||||
include Gql::Mutations::Concerns::HandlesAuthentication
|
||||
|
||||
description 'Verify a signed up user.'
|
||||
|
||||
argument :token, String, required: true, description: 'Verification token'
|
||||
|
||||
field :session, Gql::Types::SessionType, description: 'The current session, if the verification was successful.'
|
||||
field :success, Boolean, description: 'This indicates if the verification was successful.'
|
||||
|
||||
allow_public_access!
|
||||
|
||||
def resolve(token:)
|
||||
user = Service::User::SignupVerify.with_current_user(false).execute(token:)
|
||||
Service::User::SignupVerify.with_current_user(false).execute(token:)
|
||||
|
||||
create_session(user, false, 'password')
|
||||
|
||||
authenticate_result
|
||||
{ success: true }
|
||||
rescue Service::User::SignupVerify::InvalidTokenError => e
|
||||
error_response({ message: e.message })
|
||||
end
|
||||
|
||||
@@ -40396,12 +40396,12 @@
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "session",
|
||||
"description": "The current session, if the verification was successful.",
|
||||
"name": "success",
|
||||
"description": "This indicates if the verification was successful.",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Session",
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
|
||||
@@ -83,8 +83,8 @@ returns
|
||||
return false
|
||||
end
|
||||
|
||||
# persistent token not valid if user is inactive
|
||||
return false if !inactive_user && persistent && user.active == false
|
||||
# token not valid if user is inactive
|
||||
return false if !inactive_user && user&.active == false
|
||||
|
||||
# add permission check
|
||||
return false if permission && !permissions?(permission)
|
||||
|
||||
153
i18n/zammad.pot
153
i18n/zammad.pot
@@ -112,14 +112,13 @@ msgstr ""
|
||||
msgid "%s (option)"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:343
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:590
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:261
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:354
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelectItem.vue:29
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:589
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:260
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:145
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldSelect/FieldSelectInput.vue:97
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInput.vue:112
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:288
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:287
|
||||
#: app/frontend/shared/composables/useSelectOptions.ts:128
|
||||
msgid "%s (unknown)"
|
||||
msgstr ""
|
||||
@@ -1607,7 +1606,7 @@ msgid "Agent idle timeout"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/role.rb:182
|
||||
#: app/models/user.rb:807
|
||||
#: app/models/user.rb:808
|
||||
msgid "Agent limit exceeded, please check your account settings."
|
||||
msgstr ""
|
||||
|
||||
@@ -2299,7 +2298,7 @@ msgstr ""
|
||||
msgid "Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:709
|
||||
#: app/models/user.rb:710
|
||||
msgid "At least one identifier (firstname, lastname, phone, mobile or email) for user is required."
|
||||
msgstr ""
|
||||
|
||||
@@ -2316,7 +2315,7 @@ msgid "At least one object must be selected."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/role.rb:159
|
||||
#: app/models/user.rb:786
|
||||
#: app/models/user.rb:787
|
||||
msgid "At least one user needs to have admin permissions."
|
||||
msgstr ""
|
||||
|
||||
@@ -2713,8 +2712,8 @@ msgstr ""
|
||||
#: app/assets/javascripts/app/controllers/_ai/ai_agent.coffee:403
|
||||
#: app/assets/javascripts/app/controllers/_ai/provider_connections.coffee:816
|
||||
#: app/assets/javascripts/app/views/generic/object_search/item_organization_members.jst.eco:5
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:473
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:430
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:433
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:390
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
@@ -2726,9 +2725,9 @@ msgstr ""
|
||||
msgid "Back to overview"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:473
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:430
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:181
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:433
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:390
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:180
|
||||
msgid "Back to previous page"
|
||||
msgstr ""
|
||||
|
||||
@@ -3118,7 +3117,7 @@ msgstr ""
|
||||
msgid "Can you tell us what are you missing in the BETA UI?"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:87
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:92
|
||||
msgid "Can't create user!"
|
||||
msgstr ""
|
||||
|
||||
@@ -3725,10 +3724,10 @@ msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonTable/CellContent/BulkCheckbox.vue:61
|
||||
#: app/frontend/apps/desktop/components/CommonTable/TableHeader.vue:435
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:681
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:680
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldDate/FieldDateTimeInput.vue:387
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:314
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInput.vue:376
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:313
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInput.vue:375
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:151
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldDate/FieldDateTimeInput.vue:165
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldSelect/FieldSelectInput.vue:124
|
||||
@@ -4415,7 +4414,7 @@ msgstr ""
|
||||
msgid "Core workflow"
|
||||
msgstr ""
|
||||
|
||||
#: app/services/service/user/signup.rb:53
|
||||
#: app/services/service/user/signup.rb:41
|
||||
msgid "Could not create user and send verification email because import_mode setting is on."
|
||||
msgstr ""
|
||||
|
||||
@@ -4469,7 +4468,7 @@ msgstr ""
|
||||
msgid "Could not remove two-factor authentication method."
|
||||
msgstr ""
|
||||
|
||||
#: app/services/service/user/signup.rb:51
|
||||
#: app/services/service/user/signup.rb:39
|
||||
msgid "Could not send user verification email because import_mode setting is on."
|
||||
msgstr ""
|
||||
|
||||
@@ -6964,7 +6963,7 @@ msgstr ""
|
||||
msgid "Email address"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:719
|
||||
#: app/models/user.rb:720
|
||||
msgid "Email address '%{email}' is already used for another user."
|
||||
msgstr ""
|
||||
|
||||
@@ -6977,7 +6976,7 @@ msgid "Email channel"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/email_verify.coffee:53
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:49
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:46
|
||||
msgid "Email could not be verified. Please contact your administrator."
|
||||
msgstr ""
|
||||
|
||||
@@ -7015,7 +7014,7 @@ msgid "Email sent to %s"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/authentication/routes.ts:111
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:110
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:90
|
||||
msgid "Email verification"
|
||||
msgstr ""
|
||||
|
||||
@@ -8822,9 +8821,9 @@ msgstr ""
|
||||
msgid "Has processed"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelectItem.vue:144
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdownItem.vue:149
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:309
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelectItem.vue:160
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdownItem.vue:167
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:308
|
||||
msgid "Has submenu"
|
||||
msgstr ""
|
||||
|
||||
@@ -9838,7 +9837,7 @@ msgstr ""
|
||||
msgid "Invalid client_id received!"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:658
|
||||
#: app/models/user.rb:659
|
||||
msgid "Invalid email '%{email}'"
|
||||
msgstr ""
|
||||
|
||||
@@ -9944,7 +9943,7 @@ msgstr ""
|
||||
msgid "Invitation sent to %s"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:78
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:83
|
||||
#: app/frontend/apps/desktop/pages/guided-setup/views/GuidedSetupManual/GuidedSetupManualInviteColleagues.vue:65
|
||||
msgid "Invitation sent!"
|
||||
msgstr ""
|
||||
@@ -9960,7 +9959,7 @@ msgstr ""
|
||||
msgid "Invite"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:10
|
||||
#: app/assets/javascripts/app/controllers/getting_started/agent.coffee:15
|
||||
msgid "Invite Agents"
|
||||
msgstr ""
|
||||
|
||||
@@ -10760,7 +10759,7 @@ msgstr ""
|
||||
msgid "Locale could not be found."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:257
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:264
|
||||
msgid "Locale not found"
|
||||
msgstr ""
|
||||
|
||||
@@ -10834,7 +10833,7 @@ msgstr ""
|
||||
msgid "Lost network connection"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/websocket.coffee:350
|
||||
#: app/assets/javascripts/app/lib/app_post/websocket.coffee:349
|
||||
msgid "Lost network connection!"
|
||||
msgstr ""
|
||||
|
||||
@@ -11681,7 +11680,7 @@ msgstr ""
|
||||
msgid "More information can be found here."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:738
|
||||
#: app/models/user.rb:739
|
||||
msgid "More than 250 secondary organizations are not allowed."
|
||||
msgstr ""
|
||||
|
||||
@@ -12404,7 +12403,7 @@ msgstr ""
|
||||
msgid "No editable knowledge base categories available."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:273
|
||||
#: app/controllers/users_controller.rb:271
|
||||
msgid "No email!"
|
||||
msgstr ""
|
||||
|
||||
@@ -12518,17 +12517,17 @@ msgid "No results for this query."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/base/jquery.textmodule.js:716
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:376
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:336
|
||||
#: app/frontend/apps/desktop/components/CommonSimpleEntityList/plugins/ticket.ts:8
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldEditor/FieldEditorSuggestionList.vue:70
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldKnowledgeBaseCategoryIcon/FieldKnowledgeBaseCategoryIconWrapper.vue:56
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:491
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:452
|
||||
#: app/frontend/apps/desktop/components/NavigationMenu/NavigationMenu.vue:90
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/TicketDetailTopBar/components/ArticleTranslationTargetMenu.vue:166
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarExternalReferences/TicketSidebarIdoit/IdoitFlyout/IdoitObjectList.vue:38
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInputDialog.vue:365
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldEditor/FieldEditorSuggestionList.vue:57
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:319
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:318
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
|
||||
@@ -12567,7 +12566,7 @@ msgstr ""
|
||||
#: app/controllers/text_modules_controller.rb:79
|
||||
#: app/controllers/ticket_articles_controller.rb:240
|
||||
#: app/controllers/tickets_controller.rb:461
|
||||
#: app/controllers/users_controller.rb:561
|
||||
#: app/controllers/users_controller.rb:559
|
||||
msgid "No source data submitted!"
|
||||
msgstr ""
|
||||
|
||||
@@ -12682,12 +12681,12 @@ msgstr ""
|
||||
msgid "Not Assigned"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:247
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:254
|
||||
msgid "Not Available"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_profile/out_of_office.coffee:160
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:240
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:247
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom.coffee:127
|
||||
#: app/helpers/knowledge_base_public_page_title_helper.rb:25
|
||||
msgid "Not Found"
|
||||
@@ -12710,7 +12709,7 @@ msgstr ""
|
||||
#: app/assets/javascripts/app/controllers/ticket_zoom.coffee:123
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_checklist/show_row_display_value.jst.eco:16
|
||||
#: app/controllers/application_controller/handles_errors.rb:72
|
||||
#: app/services/service/ticket/article/create.rb:136
|
||||
#: app/services/service/ticket/article/create.rb:138
|
||||
msgid "Not authorized"
|
||||
msgstr ""
|
||||
|
||||
@@ -13180,7 +13179,7 @@ msgstr ""
|
||||
msgid "Open in new tab"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:258
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:265
|
||||
msgid "Open in primary locale"
|
||||
msgstr ""
|
||||
|
||||
@@ -14081,13 +14080,13 @@ msgstr ""
|
||||
msgid "Please provide a recipient in \"TO\" or \"CC\"."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:369
|
||||
#: app/controllers/users_controller.rb:367
|
||||
msgid "Please provide your current password."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_profile/password.coffee:105
|
||||
#: app/assets/javascripts/app/controllers/password_reset_verify.coffee:87
|
||||
#: app/controllers/users_controller.rb:374
|
||||
#: app/controllers/users_controller.rb:372
|
||||
msgid "Please provide your new password."
|
||||
msgstr ""
|
||||
|
||||
@@ -14120,6 +14119,10 @@ msgstr ""
|
||||
msgid "Please select the authentication method that should be used to establish the connection to your Exchange server."
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:96
|
||||
msgid "Please sign in to continue."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/channel_account/user_mismatch.jst.eco:4
|
||||
msgid "Please start the reauthentication again and enter the correct credentials."
|
||||
msgstr ""
|
||||
@@ -15759,7 +15762,7 @@ msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_plugin/keyboard_shortcuts.coffee:169
|
||||
#: app/assets/javascripts/app/controllers/clues/first_steps_clues.coffee:32
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:201
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:208
|
||||
#: app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco:3
|
||||
#: app/assets/javascripts/app/views/knowledge_base/search_field_widget.jst.eco:1
|
||||
#: app/assets/javascripts/app/views/widget/text_module.jst.eco:4
|
||||
@@ -15999,11 +16002,11 @@ msgstr ""
|
||||
msgid "Secondary organizations"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:726
|
||||
#: app/models/user.rb:727
|
||||
msgid "Secondary organizations are only allowed when the primary organization is given."
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:732
|
||||
#: app/models/user.rb:733
|
||||
msgid "Secondary organizations cannot include the primary organization."
|
||||
msgstr ""
|
||||
|
||||
@@ -16254,12 +16257,12 @@ msgstr ""
|
||||
msgid "Selects the ticket number generator module."
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:502
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:455
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:462
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:415
|
||||
#: app/frontend/apps/mobile/components/CommonSelect/CommonSelect.vue:197
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInputDialog.vue:274
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTags/FieldTagsDialog.vue:171
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:201
|
||||
#: app/frontend/apps/mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue:200
|
||||
msgid "Select…"
|
||||
msgstr ""
|
||||
|
||||
@@ -17262,7 +17265,7 @@ msgstr ""
|
||||
msgid "Start typing to search or enter an email address…"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:78
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:72
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldCustomer/FieldCustomerWrapper.vue:75
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldKnowledgeBaseAnswer/FieldKnowledgeBaseAnswerWrapper.vue:39
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTags/FieldTagsWrapper.vue:37
|
||||
@@ -18329,7 +18332,7 @@ msgid "The context window size must be a positive number."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/password_check.coffee:45
|
||||
#: app/controllers/users_controller.rb:389
|
||||
#: app/controllers/users_controller.rb:387
|
||||
#: app/graphql/gql/mutations/user/current/change_password.rb:29
|
||||
msgid "The current password you provided is incorrect."
|
||||
msgstr ""
|
||||
@@ -18387,7 +18390,7 @@ msgid "The divider between TicketHook and ticket number. E. g. ': '."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/ai_analytics_download.coffee:26
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1694
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1713
|
||||
msgid "The download could not be started. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
@@ -18395,7 +18398,7 @@ msgstr ""
|
||||
msgid "The email address |%s| is already in use by another account."
|
||||
msgstr ""
|
||||
|
||||
#: app/services/service/user/password_reset/send.rb:40
|
||||
#: app/services/service/user/password_reset/send.rb:42
|
||||
msgid "The email could not be sent to the user because import_mode setting is on."
|
||||
msgstr ""
|
||||
|
||||
@@ -18677,11 +18680,11 @@ msgstr ""
|
||||
msgid "The order of your ticket overviews was reset."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:248
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:255
|
||||
msgid "The page is not available anymore"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:243
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:250
|
||||
msgid "The page was not found"
|
||||
msgstr ""
|
||||
|
||||
@@ -18880,7 +18883,7 @@ msgstr ""
|
||||
msgid "The required PGP passphrase is missing."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:660
|
||||
#: app/controllers/users_controller.rb:658
|
||||
#: app/services/service/user/add_first_admin.rb:18
|
||||
msgid "The required attribute 'email' is missing."
|
||||
msgstr ""
|
||||
@@ -18955,7 +18958,7 @@ msgstr ""
|
||||
msgid "The required parameter 'group_id' is missing."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:502
|
||||
#: app/controllers/users_controller.rb:500
|
||||
msgid "The required parameter 'id' is missing."
|
||||
msgstr ""
|
||||
|
||||
@@ -18987,7 +18990,7 @@ msgstr ""
|
||||
msgid "The required parameter 'params' is missing."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:397
|
||||
#: app/controllers/users_controller.rb:395
|
||||
msgid "The required parameter 'password' is missing."
|
||||
msgstr ""
|
||||
|
||||
@@ -19003,7 +19006,7 @@ msgstr ""
|
||||
msgid "The required parameter 'permission' is missing."
|
||||
msgstr ""
|
||||
|
||||
#: app/controllers/users_controller.rb:653
|
||||
#: app/controllers/users_controller.rb:651
|
||||
msgid "The required parameter 'signup' is missing."
|
||||
msgstr ""
|
||||
|
||||
@@ -19251,7 +19254,7 @@ msgstr ""
|
||||
msgid "The time picker"
|
||||
msgstr ""
|
||||
|
||||
#: app/services/service/user/signup.rb:62
|
||||
#: app/services/service/user/signup.rb:50
|
||||
msgid "The token could not be generated."
|
||||
msgstr ""
|
||||
|
||||
@@ -19494,7 +19497,7 @@ msgstr ""
|
||||
msgid "There was an unknown PGP error."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:181
|
||||
#: app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee:185
|
||||
msgid "There was no Knowledge Base created."
|
||||
msgstr ""
|
||||
|
||||
@@ -19791,7 +19794,7 @@ msgid "This group exceeds the allowed nesting depth."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/agent_ticket_create.coffee:760
|
||||
#: app/controllers/concerns/creates_ticket_articles.rb:43
|
||||
#: app/controllers/concerns/creates_ticket_articles.rb:45
|
||||
#: app/graphql/gql/mutations/ticket/concerns/handles_group.rb:24
|
||||
msgid "This group has no email address configured for outgoing communication."
|
||||
msgstr ""
|
||||
@@ -19829,8 +19832,8 @@ msgstr ""
|
||||
msgid "This is not a valid X509 certificate. Please check the certificate format."
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelectItem.vue:57
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdownItem.vue:89
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelectItem.vue:70
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdownItem.vue:102
|
||||
msgid "This item expands to show more options"
|
||||
msgstr ""
|
||||
|
||||
@@ -20993,7 +20996,7 @@ msgstr ""
|
||||
msgid "Try using your security key again."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/websocket.coffee:353
|
||||
#: app/assets/javascripts/app/lib/app_post/websocket.coffee:352
|
||||
#: app/frontend/apps/desktop/components/ConnectionLostDialog/ConnectionLostDialog.vue:25
|
||||
msgid "Trying to reconnect…"
|
||||
msgstr ""
|
||||
@@ -21304,9 +21307,9 @@ msgstr ""
|
||||
msgid "Unseen notifications count"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:600
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:268
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInput.vue:332
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldAutoComplete/FieldAutoCompleteInput.vue:599
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldSelect/FieldSelectInput.vue:267
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInput.vue:331
|
||||
msgid "Unselect option"
|
||||
msgstr ""
|
||||
|
||||
@@ -22074,7 +22077,7 @@ msgstr ""
|
||||
msgid "Verifying your configuration…"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:52
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:49
|
||||
msgid "Verifying your email…"
|
||||
msgstr ""
|
||||
|
||||
@@ -22475,7 +22478,7 @@ msgid "With |Date| the ticket numbers will be generated by the current date, the
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/email_verify.coffee:35
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:47
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/SignupVerify.vue:44
|
||||
msgid "Woo hoo! Your email address has been verified!"
|
||||
msgstr ""
|
||||
|
||||
@@ -23196,7 +23199,7 @@ msgstr ""
|
||||
msgid "at least one letter is required"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1077
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1096
|
||||
#: app/frontend/shared/composables/form/useCheckBodyAttachmentReference.ts:10
|
||||
msgid "attachment,attached,enclosed,enclosure"
|
||||
msgstr ""
|
||||
@@ -23804,7 +23807,7 @@ msgid "in range"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_ui_element/active.coffee:5
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1452
|
||||
#: app/assets/javascripts/app/lib/app_post/utils.coffee:1471
|
||||
#: app/assets/javascripts/app/models/webhook.coffee:44
|
||||
#: app/assets/javascripts/app/views/generic/object_search/item_object.jst.eco:16
|
||||
#: app/assets/javascripts/app/views/generic/object_search/item_organization.jst.eco:4
|
||||
@@ -23949,7 +23952,7 @@ msgstr ""
|
||||
msgid "is the wrong length (should be 1 character)"
|
||||
msgstr ""
|
||||
|
||||
#: app/models/user.rb:925
|
||||
#: app/models/user.rb:926
|
||||
msgid "is too long"
|
||||
msgstr ""
|
||||
|
||||
@@ -24421,8 +24424,8 @@ msgstr ""
|
||||
msgid "select"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:390
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:451
|
||||
#: app/frontend/apps/desktop/components/CommonSelect/CommonSelect.vue:350
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:411
|
||||
msgid "select all options"
|
||||
msgstr ""
|
||||
|
||||
@@ -24431,7 +24434,7 @@ msgstr ""
|
||||
msgid "select attachment…"
|
||||
msgstr ""
|
||||
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:451
|
||||
#: app/frontend/apps/desktop/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDropdown.vue:411
|
||||
msgid "select visible options"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -5,21 +5,12 @@ require 'rails_helper'
|
||||
# Session handling works only via controller, so use type: request.
|
||||
RSpec.describe Gql::Mutations::User::SignupVerify, :aggregate_failures, type: :request do
|
||||
context 'when verifying signed up user' do
|
||||
let(:user) do
|
||||
create(:role, name: 'user_preferences_device', default_at_signup: true, permission_names: ['user_preferences.device'])
|
||||
create(:user, verified: false)
|
||||
end
|
||||
let(:user) { create(:user, verified: false) }
|
||||
let(:query) do
|
||||
<<~QUERY
|
||||
mutation userSignupVerify($token: String!) {
|
||||
userSignupVerify(token: $token) {
|
||||
session {
|
||||
id
|
||||
afterAuth {
|
||||
type
|
||||
data
|
||||
}
|
||||
}
|
||||
success
|
||||
errors {
|
||||
message
|
||||
}
|
||||
@@ -30,30 +21,32 @@ RSpec.describe Gql::Mutations::User::SignupVerify, :aggregate_failures, type: :r
|
||||
|
||||
let(:variables) { { token: token } }
|
||||
|
||||
let(:headers) do
|
||||
{
|
||||
'X-Browser-Fingerprint' => 'some-fingerprint',
|
||||
}
|
||||
end
|
||||
|
||||
let(:graphql_response) do
|
||||
execute_graphql_query
|
||||
json_response
|
||||
end
|
||||
|
||||
def execute_graphql_query
|
||||
post '/graphql', params: { query: query, variables: variables }, headers: headers, as: :json
|
||||
post '/graphql', params: { query: query, variables: variables }, as: :json
|
||||
end
|
||||
|
||||
shared_examples 'returning an error' do |message|
|
||||
it 'returns an error' do
|
||||
expect(graphql_response['data']['userSignupVerify']).to include({ 'errors' => include({ 'message' => message }) }).and include({ 'session' => nil })
|
||||
expect(graphql_response['data']['userSignupVerify']).to include({ 'errors' => include({ 'message' => message }) }).and include({ 'success' => nil })
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returning a session' do
|
||||
it 'returns the session' do
|
||||
expect(graphql_response['data']['userSignupVerify']).to include({ 'session' => include({ 'id' => a_kind_of(String) }) }).and include({ 'errors' => nil })
|
||||
shared_examples 'returning success' do
|
||||
it 'returns success' do
|
||||
expect(graphql_response['data']['userSignupVerify']).to include({ 'success' => true }).and include({ 'errors' => nil })
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'not signing the user in' do
|
||||
it 'does not establish a session' do
|
||||
execute_graphql_query
|
||||
|
||||
expect(session[:user_id]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,7 +65,8 @@ RSpec.describe Gql::Mutations::User::SignupVerify, :aggregate_failures, type: :r
|
||||
context 'with a valid token' do
|
||||
let(:token) { User.signup_new_token(user)[:token].token } # NB: Don't ask!
|
||||
|
||||
it_behaves_like 'returning a session'
|
||||
it_behaves_like 'returning success'
|
||||
it_behaves_like 'not signing the user in'
|
||||
end
|
||||
|
||||
context 'with an invalid token' do
|
||||
|
||||
@@ -81,6 +81,32 @@ RSpec.describe Token, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'inactive user handling' do
|
||||
let(:user) { create(:user, active: false) }
|
||||
|
||||
context 'for persistent token' do
|
||||
subject(:token) { create(:ical_token, user: user) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(described_class.check(action: token.action, token: token.token)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'for non-persistent token' do
|
||||
subject(:token) { create(:password_reset_token, user: user, persistent: false) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(described_class.check(action: token.action, token: token.token)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with inactive_user option' do
|
||||
it 'returns the token’s user' do
|
||||
expect(described_class.check(action: token.action, token: token.token, inactive_user: true)).to eq(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'permission matching' do
|
||||
subject(:token) { create(:api_token, user: agent, preferences: preferences) }
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ RSpec.describe 'User email verify endpoint', authenticated_as: false, type: :req
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'not signing the user in' do
|
||||
it 'does not establish a session' do
|
||||
expect(session[:user_id]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
disable_user_create_account if defined?(disable_user_create_account)
|
||||
post api_v1_users_email_verify_path, params: params
|
||||
@@ -38,6 +44,11 @@ RSpec.describe 'User email verify endpoint', authenticated_as: false, type: :req
|
||||
let(:token) { User.signup_new_token(user)[:token].token } # NB: Don't ask!
|
||||
|
||||
it_behaves_like 'returning success'
|
||||
it_behaves_like 'not signing the user in'
|
||||
|
||||
it 'marks the user as verified' do
|
||||
expect(user.reload).to be_verified
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a token parameter' do
|
||||
|
||||
@@ -48,6 +48,18 @@ RSpec.describe Service::User::SignupVerify do
|
||||
it_behaves_like 'raising an error', Service::User::SignupVerify::InvalidTokenError, 'The provided token is invalid.'
|
||||
end
|
||||
|
||||
context 'with an inactive user' do
|
||||
let(:user) { create(:user, verified: false, active: false) }
|
||||
let(:token) { User.signup_new_token(user)[:token].token } # NB: Don't ask!
|
||||
|
||||
it_behaves_like 'raising an error', Service::User::SignupVerify::InvalidTokenError, 'The provided token is invalid.'
|
||||
|
||||
it 'does not verify the user', :aggregate_failures do
|
||||
expect { service_result }.to raise_error(Service::User::SignupVerify::InvalidTokenError)
|
||||
expect(user.reload).not_to be_verified
|
||||
end
|
||||
end
|
||||
|
||||
context 'with current user' do
|
||||
context 'when same as the user being verified' do
|
||||
subject(:service_result) { described_class.with_current_user(user).execute(token:) }
|
||||
|
||||
@@ -31,7 +31,8 @@ RSpec.describe 'Signup', authenticated_as: false, performs_jobs: true, type: :sy
|
||||
|
||||
visit notification_url
|
||||
|
||||
expect_current_route 'ticket/view/my_tickets'
|
||||
expect(page).to have_text('Your email address has been verified!')
|
||||
expect_current_route 'login'
|
||||
end
|
||||
|
||||
it 'with a weak password show password strength error' do
|
||||
|
||||
Reference in New Issue
Block a user