make authenticateHumhubAutoLogin a mutation, auto register user on humhub if project was set and user don't exist on humhub

This commit is contained in:
einhornimmond 2025-04-22 10:10:16 +02:00
parent 6fdcb0a86d
commit 9ff6470b47
8 changed files with 67 additions and 222 deletions

View File

@ -47,13 +47,13 @@ import { UserContact } from '@model/UserContact'
import { UserLocationResult } from '@model/UserLocationResult'
import { HumHubClient } from '@/apis/humhub/HumHubClient'
import { Account as HumhubAccount } from '@/apis/humhub/model/Account'
import { GetUser } from '@/apis/humhub/model/GetUser'
import { PostUser } from '@/apis/humhub/model/PostUser'
import { subscribe } from '@/apis/KlicktippController'
import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config'
import { PublishNameLogic } from '@/data/PublishName.logic'
import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
@ -75,7 +75,6 @@ import {
EVENT_ADMIN_USER_DELETE,
EVENT_ADMIN_USER_UNDELETE,
} from '@/event/Events'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { isValidPassword } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
@ -821,7 +820,7 @@ export class UserResolver {
}
@Authorized([RIGHTS.HUMHUB_AUTO_LOGIN])
@Query(() => String)
@Mutation(() => String)
async authenticateHumhubAutoLogin(
@Ctx() context: Context,
@Arg('project', () => String, { nullable: true }) project?: string | null,
@ -832,19 +831,23 @@ export class UserResolver {
if (!humhubClient) {
throw new LogError('cannot create humhub client')
}
const userNameLogic = new PublishNameLogic(dbUser)
const username = userNameLogic.getUserIdentifier(dbUser.humhubPublishName as PublishNameType)
let humhubUser = await humhubClient.userByUsername(username)
if (!humhubUser) {
humhubUser = await humhubClient.userByEmail(dbUser.emailContact.email)
// should rarely happen, so we don't optimize for parallel processing
if (!dbUser.humhubAllowed && project) {
await ProjectBranding.findOneOrFail({ where: { alias: project } })
dbUser.humhubAllowed = true
await dbUser.save()
}
const humhubUserAccount = new HumhubAccount(dbUser)
const autoLoginUrlPromise = humhubClient.createAutoLoginUrl(humhubUserAccount.username, project)
const humhubUser = await syncHumhub(null, dbUser)
if (!humhubUser) {
throw new LogError("user don't exist (any longer) on humhub")
throw new LogError("user don't exist (any longer) on humhub and couldn't be created")
}
if (humhubUser.account.status !== 1) {
throw new LogError('user status is not 1', humhubUser.account.status)
}
return await humhubClient.createAutoLoginUrl(humhubUser.account.username, project)
const autoLoginUrl = await autoLoginUrlPromise
return autoLoginUrl
}
@Authorized([RIGHTS.SEARCH_ADMIN_USERS])

View File

@ -3,7 +3,9 @@ import { User } from '@entity/User'
import { HumHubClient } from '@/apis/humhub/HumHubClient'
import { GetUser } from '@/apis/humhub/model/GetUser'
import { ExecutedHumhubAction, syncUser } from '@/apis/humhub/syncUser'
import { PublishNameLogic } from '@/data/PublishName.logic'
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { backendLogger as logger } from '@/server/logger'
/**
@ -16,7 +18,7 @@ export async function syncHumhub(
updateUserInfosArg: UpdateUserInfosArgs | null,
user: User,
spaceId?: number | null,
): Promise<number | undefined> {
): Promise<GetUser | null | undefined> {
// check for humhub relevant changes
if (
updateUserInfosArg &&
@ -36,7 +38,9 @@ export async function syncHumhub(
return
}
logger.debug('retrieve user from humhub')
let humhubUser = await humhubClient.userByUsername(user.alias ?? user.gradidoID)
const userNameLogic = new PublishNameLogic(user)
const username = userNameLogic.getUserIdentifier(user.humhubPublishName as PublishNameType)
let humhubUser = await humhubClient.userByUsername(username)
if (!humhubUser) {
humhubUser = await humhubClient.userByEmail(user.emailContact.email)
}
@ -58,5 +62,8 @@ export async function syncHumhub(
await humhubClient.addUserToSpace(humhubUser.id, spaceId)
logger.debug(`user added to space ${spaceId}`)
}
return user.id
if (result !== ExecutedHumhubAction.SKIP) {
return await humhubClient.userByUsername(username)
}
return humhubUser
}

View File

@ -32,10 +32,10 @@
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { ref, computed, onMounted } from 'vue'
import { useMutation } from '@vue/apollo-composable'
import { useStore } from 'vuex'
import { authenticateHumhubAutoLogin } from '@/graphql/queries'
import { authenticateHumhubAutoLogin } from '@/graphql/mutations'
const store = useStore()
@ -44,12 +44,21 @@ const humhubUri = ref('')
const humhubAllowed = computed(() => store.state.humhubAllowed)
const { onResult, onError } = useQuery(authenticateHumhubAutoLogin, null, {
fetchPolicy: 'network-only',
enabled: true,
})
const {
onDone,
mutate: mutateHumhubAutoLogin,
onError,
called,
} = useMutation(
authenticateHumhubAutoLogin,
{},
{
fetchPolicy: 'network-only',
enabled: true,
},
)
onResult(({ data }) => {
onDone(({ data }) => {
if (data) {
humhubUri.value = data.authenticateHumhubAutoLogin
enableButton.value = true
@ -61,6 +70,12 @@ onError(() => {
humhubUri.value = ''
store.commit('humhubAllowed', false)
})
onMounted(() => {
if (!called.value) {
mutateHumhubAutoLogin()
}
})
</script>
<style scoped>
.card {

View File

@ -60,6 +60,17 @@ export const updateUserInfos = gql`
}
`
export const authenticateHumhubAutoLogin = gql`
mutation {
authenticateHumhubAutoLogin
}
`
export const authenticateHumhubAutoLoginProject = gql`
mutation ($project: String!) {
authenticateHumhubAutoLogin(project: $project)
}
`
export const createUser = gql`
mutation (
$firstName: String!

View File

@ -37,17 +37,6 @@ export const userLocationQuery = gql`
}
`
export const authenticateHumhubAutoLogin = gql`
query {
authenticateHumhubAutoLogin
}
`
export const authenticateHumhubAutoLoginProject = gql`
query ($project: String!) {
authenticateHumhubAutoLogin(project: $project)
}
`
export const transactionsQuery = gql`
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {

View File

@ -1,103 +0,0 @@
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { nextTick } from 'vue'
import Circles from './Circles'
import { authenticateHumhubAutoLogin } from '@/graphql/queries'
import { createStore } from 'vuex'
import { useQuery } from '@vue/apollo-composable'
vi.mock('@vue/apollo-composable', () => ({
useQuery: vi.fn(),
}))
const TEST_URL_WITH_JWT_TOKEN =
'https://community.gradido.net/user/auth/external?authclient=jwt&jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTY4NTI0NjQxMn0.V2h4Rf3LfdOYDsx2clVCx-jbhKoY5F4Ks5-YJGVtHRk'
describe('Circles', () => {
let wrapper
let store
let mockOnResult
let mockRefetch
let mockOnError
beforeEach(() => {
store = createStore({
state: {
humhubAllowed: true,
},
mutations: {
humhubAllowed(state, value) {
state.humhubAllowed = value
},
},
})
mockOnResult = vi.fn()
mockRefetch = vi.fn()
mockOnError = vi.fn()
vi.mocked(useQuery).mockReturnValue({
refetch: mockRefetch,
onResult: mockOnResult,
onError: mockOnError,
})
wrapper = mount(Circles, {
global: {
plugins: [store],
mocks: {
$t: (key) => key,
$i18n: {
locale: 'en',
},
},
stubs: {
BContainer: true,
BRow: true,
BCol: true,
BButton: true,
RouterLink: true,
},
},
})
})
afterEach(() => {
vi.clearAllMocks()
})
it('renders the circles page', () => {
expect(wrapper.find('div.circles').exists()).toBe(true)
})
it('calls authenticateHumhubAutoLogin on mount', () => {
expect(useQuery).toHaveBeenCalledWith(
authenticateHumhubAutoLogin,
null,
expect.objectContaining({
fetchPolicy: 'network-only',
enabled: true,
}),
)
expect(mockRefetch).toHaveBeenCalled()
})
it('sets humhubUri and enables button on success', async () => {
const successCallback = mockOnResult.mock.calls[0][0]
successCallback({ data: { authenticateHumhubAutoLogin: TEST_URL_WITH_JWT_TOKEN } })
await nextTick()
expect(wrapper.vm.humhubUri).toBe(TEST_URL_WITH_JWT_TOKEN)
expect(wrapper.vm.enableButton).toBe(true)
})
it('handles error in authenticateHumhubAutoLogin', async () => {
const errorCallback = mockOnError.mock.calls[0][0]
errorCallback(new Error('Test error'))
await nextTick()
expect(wrapper.vm.enableButton).toBe(true)
expect(wrapper.vm.humhubUri).toBe('')
expect(store.state.humhubAllowed).toBe(false)
})
})

View File

@ -1,79 +0,0 @@
<template>
<div class="circles">
<BContainer class="bg-white app-box-shadow gradido-border-radius p-4 mt--3">
<div class="h3">{{ $t('circles.headline') }}</div>
<div class="my-4 text-small">
<span v-for="(line, lineNumber) of $t('circles.text').split('\n')" :key="lineNumber">
{{ line }}
<br />
</span>
</div>
<BRow class="my-5">
<BCol cols="12">
<div class="text-lg-end">
<BButton
v-if="humhubAllowed"
:href="humhubUri"
variant="gradido"
:disabled="enableButton === false"
target="_blank"
>
{{ $t('circles.button') }}
</BButton>
<RouterLink v-else to="/settings/extern">
<BButton variant="gradido">
{{ $t('circles.button') }}
</BButton>
</RouterLink>
</div>
</BCol>
</BRow>
</BContainer>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { useStore } from 'vuex'
import { authenticateHumhubAutoLogin } from '@/graphql/queries'
const store = useStore()
const enableButton = ref(false)
const humhubUri = ref('')
const humhubAllowed = computed(() => store.state.humhubAllowed)
const {
refetch: refetchAuthenticateHumhub,
onResult,
onError,
} = useQuery(authenticateHumhubAutoLogin, null, {
fetchPolicy: 'network-only',
enabled: true,
})
onResult(({ data }) => {
if (data) {
humhubUri.value = data.authenticateHumhubAutoLogin
enableButton.value = true
}
})
onError(() => {
enableButton.value = true
humhubUri.value = ''
store.commit('humhubAllowed', false)
})
const handleAuthenticateHumhubAutoLogin = async () => {
enableButton.value = false
humhubUri.value = null
await refetchAuthenticateHumhub()
}
onMounted(() => {
handleAuthenticateHumhubAutoLogin()
})
</script>

View File

@ -61,14 +61,13 @@
import InputPassword from '@/components/Inputs/InputPassword'
import InputEmail from '@/components/Inputs/InputEmail'
import Message from '@/components/Message/Message'
import { login } from '@/graphql/mutations'
import { authenticateHumhubAutoLoginProject } from '@/graphql/queries'
import { login, authenticateHumhubAutoLoginProject } from '@/graphql/mutations '
import { ref, computed } from 'vue'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import { useRouter, useRoute } from 'vue-router'
import { useForm } from 'vee-validate'
import { useMutation, useLazyQuery } from '@vue/apollo-composable'
import { useMutation } from '@vue/apollo-composable'
import { useAppToast } from '@/composables/useToast'
import { useAuthLinks } from '@/composables/useAuthLinks'
import CONFIG from '@/config'
@ -80,7 +79,12 @@ const route = useRoute()
const store = useStore()
const { t } = useI18n()
const { mutate } = useMutation(login)
const { load } = useLazyQuery(authenticateHumhubAutoLoginProject)
const { mutate: mutateHumhubAutoLogin } = useMutation(authenticateHumhubAutoLoginProject, {
variables: {
project: store.state.project,
},
enabled: store.state.project,
})
// const $loading = useLoading() // TODO needs to be updated but there is some sort of an issue that breaks the app.
const { toastError } = useAppToast()
const { register } = useAuthLinks()
@ -117,10 +121,8 @@ const onSubmit = handleSubmit(async (values) => {
// await loader.hide()
if (store.state.project) {
const result = await load(authenticateHumhubAutoLoginProject, {
project: store.state.project,
})
window.location.href = result.authenticateHumhubAutoLogin
const result = await mutateHumhubAutoLogin()
window.location.href = result.data.authenticateHumhubAutoLogin
return
}