Skip to content

Commit 319bdda

Browse files
authored
Merge pull request #11992 from owncloud/feat/ocm-invite-token
fix(ocm): base64 encoded invite token
2 parents 40b3628 + 690dec7 commit 319bdda

File tree

4 files changed

+76
-192
lines changed

4 files changed

+76
-192
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Bugfix: OCM invite flow
2+
3+
We've fixed the OCM invite flow and made it more user-friendly.
4+
5+
https://github.com/owncloud/web/pull/11992
6+
https://github.com/owncloud/web/issues/11983

packages/web-app-ocm/src/schemas.ts

-10
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,3 @@ export type InviteSchema = z.infer<typeof inviteSchema>
1111

1212
export const inviteListSchema = z.array(inviteSchema)
1313
export type InviteListSchema = z.infer<typeof inviteListSchema>
14-
15-
// Provider
16-
export const providerSchema = z.object({
17-
domain: z.string(),
18-
full_name: z.string()
19-
})
20-
export type ProviderSchema = z.infer<typeof providerSchema>
21-
22-
export const providerListSchema = z.array(providerSchema)
23-
export type ProviderListSchema = z.infer<typeof providerListSchema>

packages/web-app-ocm/src/views/IncomingInvitations.vue

+42-133
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,38 @@
11
<template>
22
<div id="incoming" class="sciencemesh-app">
33
<div>
4-
<div class="oc-flex oc-flex-middle oc-px-m oc-py-s">
4+
<div class="oc-flex oc-flex-middle oc-px-m oc-pt-s">
55
<oc-icon name="user-received" />
66
<h2 class="oc-px-s" v-text="$gettext('Accept invitations')" />
77
<oc-contextual-helper class="oc-pl-xs" v-bind="helperContent" />
88
</div>
9-
<div v-if="!providers.length" class="oc-flex oc-flex-center oc-flex-middle">
10-
<oc-icon name="error-warning" fill-type="line" class="oc-mr-s" size="large" />
11-
<span v-text="$gettext('The list of institutions is empty. Please contact your admin.')" />
12-
</div>
13-
<div v-else class="oc-flex oc-flex-column oc-flex-middle oc-flex-center oc-p-m">
9+
<div class="oc-flex oc-flex-column oc-flex-middle oc-flex-center oc-p-m">
1410
<div class="oc-width-1-2">
1511
<oc-text-input
16-
ref="tokenInput"
1712
v-model="token"
1813
:label="$gettext('Enter invite token')"
1914
:clear-button-enabled="true"
20-
class="oc-mb-m"
15+
class="oc-mb-s"
16+
@update:model-value="decodeInviteToken"
2117
/>
22-
<oc-select
23-
v-model="provider"
24-
:label="$gettext('Select institution of inviter')"
25-
:options="providers"
26-
class="oc-mb-m"
27-
:position-fixed="true"
28-
:loading="loading"
18+
<div
19+
:class="{
20+
'oc-text-input-danger': providerError && token,
21+
'oc-text-input-success': provider
22+
}"
2923
>
30-
<template #option="{ full_name, domain }">
31-
<div class="oc-text-break">
32-
<span class="option">
33-
<strong v-text="full_name" />
34-
</span>
35-
<span class="option" v-text="domain" />
36-
</div>
37-
</template>
38-
<template #no-options> No institutions found with this name</template>
39-
<template #selected-option="{ full_name, domain }">
40-
<div class="options-wrapper oc-text-break">
41-
<strong class="oc-mr-s oc-text-break" v-text="full_name" />
42-
<small
43-
v-oc-tooltip="domain"
44-
v-text="domain.length > 17 ? domain.slice(0, 20) + '...' : domain"
45-
/>
46-
</div>
47-
</template>
48-
</oc-select>
49-
<div v-if="providerError" class="oc-text-input-message">
50-
<span
51-
class="oc-text-input-danger"
52-
v-text="$gettext('Unknown institution. Check invitation url or select from list')"
53-
/>
24+
<span v-text="$gettext('Institution:')" />
25+
<span v-if="!token" v-text="'-'" />
26+
<span v-else-if="provider" v-text="provider" />
27+
<span v-else v-text="$gettext('invalid invite token')" />
5428
</div>
5529
</div>
56-
<oc-button size="small" :disabled="acceptInvitationButtonDisabled" @click="acceptInvite">
30+
<oc-button
31+
size="small"
32+
:disabled="acceptInvitationButtonDisabled"
33+
class="oc-mt-s"
34+
@click="acceptInvite"
35+
>
5736
<oc-icon name="add" />
5837
<span v-text="$gettext('Accept invitation')" />
5938
</oc-button>
@@ -63,35 +42,23 @@
6342
</template>
6443

6544
<script lang="ts">
66-
import { computed, defineComponent, onMounted, ref, unref } from 'vue'
67-
import {
68-
queryItemAsString,
69-
useClientService,
70-
useRoute,
71-
useRouter,
72-
useMessages,
73-
useConfigStore
74-
} from '@ownclouders/web-pkg'
45+
import { computed, defineComponent, ref, unref } from 'vue'
46+
import { useClientService, useRoute, useRouter, useMessages } from '@ownclouders/web-pkg'
7547
import { useGettext } from 'vue3-gettext'
76-
import { onBeforeRouteUpdate, RouteLocationNormalized } from 'vue-router'
77-
import { ProviderSchema, providerListSchema } from '../schemas'
78-
import { OcTextInput } from '@ownclouders/design-system/components'
7948
8049
export default defineComponent({
8150
emits: ['highlightNewConnections'],
8251
setup(props, { emit }) {
8352
const { showErrorMessage } = useMessages()
8453
const router = useRouter()
54+
const route = useRoute()
8555
const clientService = useClientService()
86-
const configStore = useConfigStore()
8756
const { $gettext } = useGettext()
8857
8958
const token = ref<string>(undefined)
90-
const provider = ref<ProviderSchema>(undefined)
91-
const providers = ref<ProviderSchema[]>([])
92-
const loading = ref(true)
59+
const decodedToken = ref<string>(undefined)
60+
const provider = ref<string>(undefined)
9361
const providerError = ref(false)
94-
const tokenInput = ref<InstanceType<typeof OcTextInput>>()
9562
9663
const helperContent = computed(() => {
9764
return {
@@ -103,7 +70,7 @@ export default defineComponent({
10370
})
10471
10572
const acceptInvitationButtonDisabled = computed(() => {
106-
return !unref(token) || !unref(provider) || unref(provider).full_name === 'Unknown provider'
73+
return !unref(decodedToken) || !unref(provider)
10774
})
10875
10976
const errorPopup = (error: Error) => {
@@ -117,8 +84,8 @@ export default defineComponent({
11784
const acceptInvite = async () => {
11885
try {
11986
await clientService.httpAuthenticated.post('/sciencemesh/accept-invite', {
120-
token: unref(token),
121-
providerDomain: unref(provider).domain
87+
token: unref(decodedToken),
88+
providerDomain: unref(provider)
12289
})
12390
token.value = undefined
12491
provider.value = undefined
@@ -134,90 +101,32 @@ export default defineComponent({
134101
errorPopup(error)
135102
}
136103
}
137-
const listProviders = async () => {
138-
try {
139-
const { data: allProviders } = await clientService.httpAuthenticated.get(
140-
'/sciencemesh/list-providers',
141-
{
142-
schema: providerListSchema
143-
}
144-
)
145-
providers.value = allProviders.filter((p) => !isMyProviderSelectedProvider(p))
146-
} catch (error) {
147-
errorPopup(error)
148-
} finally {
149-
loading.value = false
150-
}
151-
}
152-
const scrollToForm = () => {
153-
const el = document.getElementById('sciencemesh-accept-invites')
154-
if (el) {
155-
el.scrollIntoView()
156-
}
157-
}
158-
const isMyProviderSelectedProvider = (p: ProviderSchema) => {
159-
// the protocol is not important, we just need the host and port, it's there to make it compatible with URL
160-
const toURL = (purl: string) =>
161-
new URL(purl.split('://').length === 1 ? `https://${purl}` : purl)
162-
const { host: configStoreHost, port: configStorePort } = toURL(configStore.serverUrl)
163-
const { host: providerSchemaHost, port: providerSchemaPort } = toURL(p.domain)
164104
165-
return [
166-
// ensure that the config store host is not empty, minimal check
167-
!!configStoreHost,
168-
// ensure that the provider schema host is not empty, minimal check
169-
!!providerSchemaHost,
170-
// check if the host is the same
171-
configStoreHost === providerSchemaHost,
172-
// also check the port, multiple instances can run on the same host but not on the same port...
173-
configStorePort === providerSchemaPort
174-
].every((c) => c)
175-
}
176-
177-
const handleParams = (to: RouteLocationNormalized) => {
178-
const tokenQuery = to.query.token
179-
if (tokenQuery) {
180-
token.value = queryItemAsString(tokenQuery)
181-
unref(tokenInput).focus()
182-
scrollToForm()
183-
}
184-
const providerDomainQuery = to.query.providerDomain
185-
if (providerDomainQuery) {
186-
const matchedProvider = unref(providers)?.find(
187-
(p) => p.domain === queryItemAsString(providerDomainQuery)
188-
)
189-
if (matchedProvider) {
190-
provider.value = matchedProvider
191-
providerError.value = false
192-
} else {
193-
provider.value = {
194-
full_name: 'Unknown provider',
195-
domain: queryItemAsString(providerDomainQuery)
196-
}
197-
providerError.value = true
105+
const decodeInviteToken = (value: string) => {
106+
try {
107+
const decoded = atob(value)
108+
if (!decoded.includes('@')) {
109+
throw new Error()
198110
}
111+
const [token, serverUrl] = decoded.split('@')
112+
provider.value = serverUrl
113+
decodedToken.value = token
114+
providerError.value = false
115+
} catch (e) {
116+
provider.value = ''
117+
decodedToken.value = ''
118+
providerError.value = true
199119
}
200120
}
201121
202-
const route = useRoute()
203-
onMounted(async () => {
204-
await listProviders()
205-
handleParams(unref(route))
206-
})
207-
onBeforeRouteUpdate((to) => {
208-
handleParams(to)
209-
})
210-
211122
return {
212-
tokenInput,
213123
helperContent,
214124
token,
215125
provider,
216-
providers,
217-
loading,
218126
providerError,
219127
acceptInvitationButtonDisabled,
220-
acceptInvite
128+
acceptInvite,
129+
decodeInviteToken
221130
}
222131
}
223132
})

0 commit comments

Comments
 (0)