@live-change/user-frontend 0.8.14 → 0.8.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front/components.d.ts +1 -0
- package/front/src/connected/Connected.vue +70 -11
- package/front/src/connected/routes.js +1 -1
- package/front/src/nav/UserMenu.vue +2 -2
- package/front/src/notifications/NotificationsIcon.vue +1 -1
- package/front/src/router.js +2 -2
- package/front/src/settings/Settings.vue +8 -7
- package/front/src/sign/GoogleAuth.vue +122 -0
- package/front/src/sign/GoogleAuthReturn.vue +104 -0
- package/front/src/sign/{SignIn.vue → SignInEmail.vue} +17 -9
- package/front/src/sign/SignOut.vue +7 -2
- package/front/src/sign/{SignUp.vue → SignUpEmail.vue} +16 -4
- package/front/src/sign/SignUpFinished.vue +7 -4
- package/front/src/sign/routes.js +10 -5
- package/front/src/utils/googleApi.js +50 -0
- package/package.json +28 -27
- package/server/app.config.js +7 -1
- package/server/services.list.js +2 -0
package/front/components.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {}
|
|
|
8
8
|
declare module 'vue' {
|
|
9
9
|
export interface GlobalComponents {
|
|
10
10
|
Button: typeof import('primevue/button')['default']
|
|
11
|
+
Divider: typeof import('primevue/divider')['default']
|
|
11
12
|
RouterLink: typeof import('vue-router')['RouterLink']
|
|
12
13
|
RouterView: typeof import('vue-router')['RouterView']
|
|
13
14
|
}
|
|
@@ -18,10 +18,20 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
<Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
|
|
20
20
|
v-if="canDelete"
|
|
21
|
-
@click="event =>
|
|
21
|
+
@click="event => disconnectContact(event, contact)" />
|
|
22
|
+
</li>
|
|
23
|
+
<li v-for="account in accounts"
|
|
24
|
+
class="flex flex-row align-items-center justify-content-between mb-2">
|
|
25
|
+
<div v-if="account.accountType.accountType === 'google'"
|
|
26
|
+
class="flex flex-row align-items-center">
|
|
27
|
+
<i class="pi pi-google mr-2"></i>
|
|
28
|
+
<span class="block text-900 font-medium text-lg">{{ account.email }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
<pre v-else>{{ account }}</pre>
|
|
31
|
+
<Button class="p-button-text p-button-plain p-button-rounded mr-1" icon="pi pi-times"
|
|
32
|
+
v-if="canDelete"
|
|
33
|
+
@click="event => disconnectAccount(event, account, account.email)" />
|
|
22
34
|
</li>
|
|
23
|
-
|
|
24
|
-
<!-- {{ contacts }}-->
|
|
25
35
|
|
|
26
36
|
</ul>
|
|
27
37
|
|
|
@@ -55,11 +65,16 @@
|
|
|
55
65
|
import { useApi, live, usePath, useActions } from '@live-change/vue3-ssr'
|
|
56
66
|
const api = useApi()
|
|
57
67
|
const path = usePath()
|
|
58
|
-
const
|
|
68
|
+
const messageAuthenticationClientConfig = api.getServiceDefinition('messageAuthentication')?.clientConfig
|
|
69
|
+
const contactTypesAvailable = messageAuthenticationClientConfig?.contactTypes || []
|
|
70
|
+
|
|
71
|
+
const userClientConfig = api.getServiceDefinition('user')?.clientConfig
|
|
72
|
+
const accountTypesAvailable = userClientConfig?.remoteAccountTypes || []
|
|
59
73
|
|
|
60
74
|
const messageAuthenticationApi = useActions().messageAuthentication
|
|
61
75
|
|
|
62
|
-
function
|
|
76
|
+
function disconnectContact(event, contactData) {
|
|
77
|
+
const { contactType, id: contact } = contactData
|
|
63
78
|
confirm.require({
|
|
64
79
|
target: event.currentTarget,
|
|
65
80
|
message: `Do you want to disconnect ${contactType.contactType} account ${contact}?`,
|
|
@@ -78,12 +93,30 @@
|
|
|
78
93
|
})
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
function disconnectAccount(event, contactData, name) {
|
|
97
|
+
const { accountType, id: account } = contactData
|
|
98
|
+
confirm.require({
|
|
99
|
+
target: event.currentTarget,
|
|
100
|
+
message: `Do you want to disconnect ${accountType.accountType} account ${name || account}?`,
|
|
101
|
+
icon: 'pi pi-info-circle',
|
|
102
|
+
acceptClass: 'p-button-danger',
|
|
103
|
+
accept: async () => {
|
|
104
|
+
const upperCaseType = accountType.accountType[0].toUpperCase() + accountType.accountType.slice(1)
|
|
105
|
+
workingZone.addPromise('disconnectAccount', (async () => {
|
|
106
|
+
await api.actions[accountType.accountType+'Authentication']['disconnect'+upperCaseType]({ account })
|
|
107
|
+
toast.add({ severity: 'info', summary: 'Account disconnected', life: 1500 })
|
|
108
|
+
})())
|
|
109
|
+
},
|
|
110
|
+
reject: () => {
|
|
111
|
+
toast.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
82
115
|
|
|
83
116
|
const contactsTypes = contactTypesAvailable.map(contactType => {
|
|
84
117
|
const contactTypeUpper = contactType[0].toUpperCase() + contactType.slice(1)
|
|
85
118
|
|
|
86
|
-
let serviceName =
|
|
119
|
+
let serviceName = contactType
|
|
87
120
|
let viewName = 'myUser'+contactTypeUpper+'s'
|
|
88
121
|
if(!path[serviceName]) { // find service by viewName
|
|
89
122
|
for(const s in path) {
|
|
@@ -93,7 +126,8 @@
|
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
|
-
console.log('contactType', contactType, 'serviceName', serviceName, 'viewName', viewName)
|
|
129
|
+
//console.log('contactType', contactType, 'serviceName', serviceName, 'viewName', viewName)
|
|
130
|
+
console.log(`path[${serviceName}][${viewName}] =`, path[serviceName][viewName])
|
|
97
131
|
return {
|
|
98
132
|
contactType,
|
|
99
133
|
serviceName,
|
|
@@ -103,16 +137,41 @@
|
|
|
103
137
|
}
|
|
104
138
|
})
|
|
105
139
|
|
|
106
|
-
|
|
140
|
+
const accountTypes = accountTypesAvailable.map(accountType => {
|
|
141
|
+
let serviceName = accountType+'Authentication'
|
|
142
|
+
let viewName = 'myUserAccounts'
|
|
143
|
+
console.log('remoteAccountType', accountType, 'serviceName', serviceName, 'viewName', viewName)
|
|
144
|
+
console.log(`path[${serviceName}][${viewName}] =`, path[serviceName][viewName])
|
|
145
|
+
return {
|
|
146
|
+
accountType,
|
|
147
|
+
serviceName,
|
|
148
|
+
viewName,
|
|
149
|
+
path: path[serviceName][viewName]({}),
|
|
150
|
+
accounts: null
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const contactPromises = contactsTypes.map(async contactType => {
|
|
107
155
|
contactType.contacts = await live(contactType.path)
|
|
108
|
-
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const accountPromises = accountTypes.map(async accountType => {
|
|
159
|
+
accountType.accounts = await live(accountType.path)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await Promise.all([ ...contactPromises, ...accountPromises ])
|
|
109
163
|
|
|
110
164
|
const contacts = computed(() => contactsTypes.map((c,i) => c.contacts.value.map(v => ({
|
|
111
165
|
contactType: c,
|
|
112
166
|
...(v)
|
|
113
167
|
}))).flat())
|
|
114
168
|
|
|
115
|
-
const
|
|
169
|
+
const accounts = computed(() => accountTypes.map((c,i) => c.accounts.value.map(v => ({
|
|
170
|
+
accountType: c,
|
|
171
|
+
...(v)
|
|
172
|
+
}))).flat())
|
|
173
|
+
|
|
174
|
+
const allAccountsCount = computed(() => contacts.value?.length + accounts.value?.length )
|
|
116
175
|
const canDelete = computed(() => allAccountsCount.value > 1 )
|
|
117
176
|
|
|
118
177
|
</script>
|
|
@@ -4,7 +4,7 @@ export function routes(config = {}) {
|
|
|
4
4
|
return [
|
|
5
5
|
|
|
6
6
|
route({ name: 'user:connected', path: prefix + 'connected',
|
|
7
|
-
component: () => import("./Connected.vue") }),
|
|
7
|
+
component: () => import("./Connected.vue"), meta: { signedIn: true } }),
|
|
8
8
|
|
|
9
9
|
route({ name: 'user:connect-email', path: prefix + 'connect-email',
|
|
10
10
|
component: () => import("./ConnectEmail.vue") }),
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
</router-link>
|
|
12
12
|
</li>
|
|
13
13
|
<li v-if="!client.user">
|
|
14
|
-
<router-link :to="{ name: 'user:
|
|
14
|
+
<router-link :to="{ name: 'user:signUpEmail' }"
|
|
15
15
|
v-ripple
|
|
16
16
|
class="flex px-6 py-2 align-items-center text-600 hover:text-900 hover:surface-100
|
|
17
17
|
font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple no-underline"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</router-link>
|
|
22
22
|
</li>
|
|
23
23
|
<li v-if="!client.user">
|
|
24
|
-
<router-link :to="{ name: 'user:
|
|
24
|
+
<router-link :to="{ name: 'user:signInEmail' }"
|
|
25
25
|
v-ripple
|
|
26
26
|
class="flex px-6 py-2 align-items-center text-600 hover:text-900 hover:surface-100
|
|
27
27
|
font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple no-underline"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
class="align-items-center flex-grow-1 justify-content-between hidden absolute w-full md:w-auto surface-overlay
|
|
13
13
|
right-0 top-100 z-1 shadow-2 overflow-x-hidden overflow-y-auto"
|
|
14
14
|
style="max-height: calc(100vh - 8rem)">
|
|
15
|
-
<loading-zone suspense
|
|
15
|
+
<loading-zone suspense>
|
|
16
16
|
<template v-slot:loading>
|
|
17
17
|
<div class="flex align-items-center justify-content-center top-0 left-0 notifications-loading">
|
|
18
18
|
<ProgressSpinner animationDuration=".5s"/>
|
package/front/src/router.js
CHANGED
|
@@ -61,7 +61,7 @@ export function installUserRedirects(router, app, config) {
|
|
|
61
61
|
if(!client.value.user) {
|
|
62
62
|
console.log("REDIRECT TO LOGIN BECAUSE PAGE REQUIRES LOGIN!")
|
|
63
63
|
router.redirectAfterSignIn = to.fullPath
|
|
64
|
-
return { name: 'user:
|
|
64
|
+
return { name: 'user:signInEmail' }
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
if(to?.matched.find(m => m?.meta.signedOut)) {
|
|
@@ -70,7 +70,7 @@ export function installUserRedirects(router, app, config) {
|
|
|
70
70
|
return { name: 'user:settings' }
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
if(to && to.name
|
|
73
|
+
if(to && to.name === 'user:signInEmail' && from?.matched.find(m => m?.meta.saveForSignIn)) {
|
|
74
74
|
console.log("SAVE FOR LOGIN", from.fullPath)
|
|
75
75
|
localStorage.redirectAfterLogin = from.fullPath
|
|
76
76
|
}
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div class="flex flex-column relative flex-auto">
|
|
9
|
-
<div v-if="viewType
|
|
9
|
+
<div v-if="viewType === 'simple'" class="p-5 flex flex-column flex-auto align-items-center">
|
|
10
10
|
<router-view></router-view>
|
|
11
11
|
</div>
|
|
12
|
-
<template v-if="viewType
|
|
12
|
+
<template v-if="viewType === 'wide'">
|
|
13
13
|
<router-view></router-view>
|
|
14
14
|
</template>
|
|
15
15
|
</div>
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
19
|
<script setup>
|
|
20
|
-
import SettingsMenu from "./SettingsMenu.vue"
|
|
21
20
|
|
|
22
|
-
import
|
|
21
|
+
import SettingsMenu from "./SettingsMenu.vue"
|
|
23
22
|
|
|
24
|
-
import {
|
|
25
|
-
const route = useRoute()
|
|
23
|
+
import { computed } from 'vue'
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
import { useRoute } from 'vue-router'
|
|
26
|
+
const route = useRoute()
|
|
27
|
+
|
|
28
|
+
const viewType = computed(() => route.meta.viewType ?? 'simple' )
|
|
28
29
|
|
|
29
30
|
</script>
|
|
30
31
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
|
|
3
|
+
<div class="surface-card p-4 shadow-2 border-round">
|
|
4
|
+
|
|
5
|
+
<div class="text-center mb-5">
|
|
6
|
+
<div class="text-900 text-3xl font-medium mb-3">Google authentication</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div v-if="state === 'canceled'" class="text-center">
|
|
10
|
+
<div>Authentication canceled by user</div>
|
|
11
|
+
<div class="flex flex-row">
|
|
12
|
+
<Button @click="back" label="Go back" icon="pi pi-arrow-left" class="w-full p-button-secondary mb-1" />
|
|
13
|
+
<Button @click="googleAuth" label="Try again" icon="pi pi-google" class="w-full p-button-secondary mb-1" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div v-else-if="state === 'waiting'" class="text-center">
|
|
17
|
+
Authentication will open in a new window.
|
|
18
|
+
</div>
|
|
19
|
+
<div v-else-if="state === 'working'" class="text-center">
|
|
20
|
+
Waiting for server...
|
|
21
|
+
</div>
|
|
22
|
+
<div v-else-if="state === 'error'" class="text-center">
|
|
23
|
+
<div>Error during authentication</div>
|
|
24
|
+
<div>{{ error }}</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div v-else>
|
|
27
|
+
Unknown authentication state: {{ state }}
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
import { loadGoogleAuth2 } from "../utils/googleApi.js"
|
|
36
|
+
import { defineProps, toRefs, ref, onMounted, inject } from 'vue'
|
|
37
|
+
|
|
38
|
+
import { useApi } from "@live-change/vue3-ssr"
|
|
39
|
+
const api = useApi()
|
|
40
|
+
|
|
41
|
+
import { useToast } from 'primevue/usetoast'
|
|
42
|
+
const toast = useToast()
|
|
43
|
+
|
|
44
|
+
const workingZone = inject('workingZone')
|
|
45
|
+
|
|
46
|
+
import { useRouter } from 'vue-router'
|
|
47
|
+
const router = useRouter()
|
|
48
|
+
|
|
49
|
+
const props = defineProps({
|
|
50
|
+
action: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: 'signInOrSignUp'
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const { action } = toRefs(props)
|
|
57
|
+
const state = ref('waiting')
|
|
58
|
+
const error = ref(null)
|
|
59
|
+
|
|
60
|
+
async function googleAuth() {
|
|
61
|
+
state.value = 'waiting'
|
|
62
|
+
const auth = await loadGoogleAuth2()
|
|
63
|
+
const googleRedirectUri = document.location.protocol + '//' + document.location.host
|
|
64
|
+
+ router.resolve({ name: 'user:googleAuthReturn', params: { action: action.value } }).href
|
|
65
|
+
const response = await (auth.signIn({
|
|
66
|
+
scope: 'profile email'
|
|
67
|
+
}).catch(error => {
|
|
68
|
+
if(error.error === 'popup_blocked_by_browser') {
|
|
69
|
+
return auth.signIn({
|
|
70
|
+
scope: 'profile email',
|
|
71
|
+
ux_mode: 'redirect',
|
|
72
|
+
redirect_uri: googleRedirectUri
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
if(error.error === 'popup_closed_by_user') {
|
|
76
|
+
toast.add({ severity: 'warning', summary: 'Canceled', detail: 'You closed login window', life: 3000 })
|
|
77
|
+
state.value = 'canceled'
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
throw error
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
state.value = 'working'
|
|
84
|
+
try {
|
|
85
|
+
const result = await workingZone.addPromise(`google ${action.value}`,
|
|
86
|
+
api.command(['googleAuthentication', action.value], {
|
|
87
|
+
accessToken: response.getAuthResponse().id_token
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
//console.log("GAUTH RESULT", result)
|
|
91
|
+
const { action: actionDone, user } = result
|
|
92
|
+
while(api.client.value.user !== result.user) {
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
94
|
+
}
|
|
95
|
+
if(actionDone === 'signIn') {
|
|
96
|
+
router.push({ name: 'user:signInFinished' })
|
|
97
|
+
} else if(actionDone === 'signUp') {
|
|
98
|
+
router.push({ name: 'user:signUpFinished' })
|
|
99
|
+
} else {
|
|
100
|
+
console.error("Unknown action", actionDone)
|
|
101
|
+
}
|
|
102
|
+
} catch(error) {
|
|
103
|
+
console.error("Google auth error", error)
|
|
104
|
+
toast.add({ severity: 'error', summary: 'Error', detail: 'Error during google authentication', life: 3000 })
|
|
105
|
+
state.value = 'error'
|
|
106
|
+
error.value = error
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function back() {
|
|
111
|
+
router.go(-1)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onMounted(() => {
|
|
115
|
+
googleAuth()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<style scoped>
|
|
121
|
+
|
|
122
|
+
</style>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
|
|
3
|
+
<div class="surface-card p-4 shadow-2 border-round">
|
|
4
|
+
|
|
5
|
+
<div class="text-center mb-5">
|
|
6
|
+
<div class="text-900 text-3xl font-medium mb-3">Google authentication</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div v-if="state === 'canceled'" class="text-center">
|
|
10
|
+
<div>Authentication canceled by user</div>
|
|
11
|
+
<div class="flex flex-row">
|
|
12
|
+
<Button @click="back" label="Go back" icon="pi pi-arrow-left" class="w-full p-button-secondary mb-1" />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div v-else-if="state === 'working'" class="text-center">
|
|
16
|
+
Waiting for server...
|
|
17
|
+
</div>
|
|
18
|
+
<div v-else-if="state === 'error'" class="text-center">
|
|
19
|
+
<div>Error during authentication</div>
|
|
20
|
+
<div>{{ error }}</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div v-else>
|
|
23
|
+
Unknown authentication state: {{ state }}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import { loadGoogleAuth2 } from "../utils/googleApi.js"
|
|
32
|
+
import { defineProps, toRefs, ref, onMounted, inject } from 'vue'
|
|
33
|
+
|
|
34
|
+
import { useApi } from "@live-change/vue3-ssr"
|
|
35
|
+
const api = useApi()
|
|
36
|
+
|
|
37
|
+
import { useToast } from 'primevue/usetoast'
|
|
38
|
+
const toast = useToast()
|
|
39
|
+
|
|
40
|
+
const workingZone = inject('workingZone')
|
|
41
|
+
|
|
42
|
+
import { useRouter } from 'vue-router'
|
|
43
|
+
const router = useRouter()
|
|
44
|
+
|
|
45
|
+
const props = defineProps({
|
|
46
|
+
action: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: 'signInOrSignUp'
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const { action } = toRefs(props)
|
|
53
|
+
const state = ref('waiting')
|
|
54
|
+
const error = ref(null)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
onMounted(async () => {
|
|
58
|
+
const id_token = decodeURIComponent(
|
|
59
|
+
document.location.hash
|
|
60
|
+
.slice(1)
|
|
61
|
+
.split('&')
|
|
62
|
+
.map(p => p.split('='))
|
|
63
|
+
.find(a => a[0] === 'id_token')
|
|
64
|
+
[1]
|
|
65
|
+
)
|
|
66
|
+
if(!id_token) {
|
|
67
|
+
state.value = 'canceled'
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const result = await workingZone.addPromise(`google ${action.value}`,
|
|
72
|
+
api.command(['googleAuthentication', action.value], {
|
|
73
|
+
accessToken: id_token
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
console.log("GAUTH RESULT", result)
|
|
77
|
+
const { action: actionDone, user } = result
|
|
78
|
+
while(api.client.value.user !== result.user) {
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
80
|
+
}
|
|
81
|
+
if(actionDone === 'signIn') {
|
|
82
|
+
router.push({ name: 'user:signInFinished' })
|
|
83
|
+
} else if(actionDone === 'signUp') {
|
|
84
|
+
router.push({ name: 'user:signUpFinished' })
|
|
85
|
+
} else {
|
|
86
|
+
console.error("Unknown action", actionDone)
|
|
87
|
+
}
|
|
88
|
+
} catch(error) {
|
|
89
|
+
console.error("Google auth error", error)
|
|
90
|
+
toast.add({ severity: 'error', summary: 'Error', detail: 'Error during google authentication', life: 3000 })
|
|
91
|
+
state.value = 'error'
|
|
92
|
+
error.value = error
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
async function back() {
|
|
97
|
+
router.go(-1)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<style scoped>
|
|
103
|
+
|
|
104
|
+
</style>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-full lg:w-6 md:w-9" v-shared-element:form="{ duration: '300ms', includeChildren: true }">
|
|
3
3
|
<div class="surface-card p-4 shadow-2 border-round">
|
|
4
|
+
|
|
4
5
|
<div class="text-center mb-5">
|
|
5
6
|
<div class="text-900 text-3xl font-medium mb-3">Welcome Back</div>
|
|
6
7
|
<span class="text-600 font-medium line-height-3">Don't have an account?</span>
|
|
7
|
-
<router-link :to="{ name: 'user:
|
|
8
|
+
<router-link :to="{ name: 'user:signUpEmail' }"
|
|
8
9
|
class="font-medium no-underline ml-2 text-blue-500 cursor-pointer">
|
|
9
10
|
Create today!</router-link>
|
|
10
11
|
</div>
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
|
|
33
34
|
<div class="flex align-items-center justify-content-between mb-6">
|
|
34
35
|
<div class="flex align-items-center">
|
|
35
|
-
<Checkbox id="rememberme" :binary="true" class="mr-2"
|
|
36
|
+
<Checkbox id="rememberme" :binary="true" class="mr-2" />
|
|
36
37
|
<label for="rememberme">Remember me</label>
|
|
37
38
|
</div>
|
|
38
39
|
<router-link :to="{ name: 'user:resetPassword' }"
|
|
@@ -43,14 +44,21 @@
|
|
|
43
44
|
|
|
44
45
|
<Button label="Sign In" icon="pi pi-user" class="w-full" type="submit"></Button>
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
</command-form>
|
|
48
|
+
|
|
49
|
+
<Divider align="center" class="my-4">
|
|
50
|
+
<span class="text-600 font-normal text-sm">OR</span>
|
|
51
|
+
</Divider>
|
|
49
52
|
|
|
50
|
-
<Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2"
|
|
51
|
-
|
|
53
|
+
<!-- <Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2" />-->
|
|
54
|
+
<router-link :to="{ name: 'user:googleAuth', params: { action: 'signInOrSignUp' } }" class="no-underline">
|
|
55
|
+
<Button
|
|
56
|
+
label="Sign In with Google"
|
|
57
|
+
icon="pi pi-google"
|
|
58
|
+
class="w-full p-button-secondary mb-1"
|
|
59
|
+
/>
|
|
60
|
+
</router-link>
|
|
52
61
|
|
|
53
|
-
</command-form>
|
|
54
62
|
</div>
|
|
55
63
|
</div>
|
|
56
64
|
</template>
|
|
@@ -72,7 +80,7 @@
|
|
|
72
80
|
|
|
73
81
|
function handleDone({ parameters, result }) {
|
|
74
82
|
console.log("DONE RESULT", result)
|
|
75
|
-
if(result.type
|
|
83
|
+
if(result.type === 'sent') {
|
|
76
84
|
const { authentication } = result
|
|
77
85
|
router.push({
|
|
78
86
|
name: 'user:sent',
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
const isMounted = ref(false)
|
|
16
16
|
onMounted(() => isMounted.value = true)
|
|
17
17
|
|
|
18
|
-
import { actions } from '@live-change/vue3-ssr'
|
|
18
|
+
import { actions, useApi } from '@live-change/vue3-ssr'
|
|
19
|
+
const api = useApi()
|
|
20
|
+
|
|
19
21
|
import { inject } from 'vue'
|
|
20
22
|
import { useRouter } from 'vue-router'
|
|
21
23
|
const router = useRouter()
|
|
@@ -27,7 +29,10 @@
|
|
|
27
29
|
if(typeof window != 'undefined') {
|
|
28
30
|
workingZone.addPromise('signOut', (async () => {
|
|
29
31
|
await signOut({})
|
|
30
|
-
|
|
32
|
+
while(api.client.value.user) {
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
34
|
+
}
|
|
35
|
+
router.push({ name: 'user:signOutFinished' })
|
|
31
36
|
})())
|
|
32
37
|
}
|
|
33
38
|
</script>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="text-center mb-5">
|
|
5
5
|
<div class="text-900 text-3xl font-medium mb-3">Sign Up</div>
|
|
6
6
|
<span class="text-600 font-medium line-height-3">Already have an account?</span>
|
|
7
|
-
<router-link :to="{ name: 'user:
|
|
7
|
+
<router-link :to="{ name: 'user:signUpEmail' }"
|
|
8
8
|
class="font-medium no-underline ml-2 text-blue-500 cursor-pointer">
|
|
9
9
|
Sign in</router-link>
|
|
10
10
|
</div>
|
|
@@ -22,18 +22,30 @@
|
|
|
22
22
|
<small v-if="data.emailError" id="email-help" class="p-error">{{ data.emailError }}</small>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
-
<Button label="Sign Up with email" icon="pi pi-user" class="w-full" type="submit"
|
|
25
|
+
<Button label="Sign Up with email" icon="pi pi-user" class="w-full" type="submit" />
|
|
26
26
|
|
|
27
27
|
</command-form>
|
|
28
|
+
|
|
29
|
+
<Divider align="center" class="my-4">
|
|
30
|
+
<span class="text-600 font-normal text-sm">OR</span>
|
|
31
|
+
</Divider>
|
|
32
|
+
|
|
33
|
+
<!-- <Button label="Sign In with GitHub" icon="pi pi-github" class="w-full p-button-secondary mb-2" />-->
|
|
34
|
+
<router-link :to="{ name: 'user:googleAuth', params: { action: 'signInOrSignUp' } }" class="no-underline">
|
|
35
|
+
<Button
|
|
36
|
+
label="Sign Up with Google"
|
|
37
|
+
icon="pi pi-google"
|
|
38
|
+
class="w-full p-button-secondary mb-1"
|
|
39
|
+
/>
|
|
40
|
+
</router-link>
|
|
41
|
+
|
|
28
42
|
</div>
|
|
29
43
|
</div>
|
|
30
44
|
</template>
|
|
31
45
|
|
|
32
46
|
<script setup>
|
|
33
47
|
import InputText from "primevue/inputtext"
|
|
34
|
-
import Checkbox from "primevue/checkbox"
|
|
35
48
|
import Button from "primevue/button"
|
|
36
|
-
import Divider from "primevue/divider"
|
|
37
49
|
|
|
38
50
|
import { useRouter } from 'vue-router'
|
|
39
51
|
const router = useRouter()
|
|
@@ -97,16 +97,19 @@
|
|
|
97
97
|
form.value.addValidator('passwordHash', () => {
|
|
98
98
|
const value = form.value.getFieldValue('passwordHash')
|
|
99
99
|
console.log("PASSWORDS MATCH?", secondPassword.value, value)
|
|
100
|
-
if(value
|
|
100
|
+
if(value !== secondPassword.value) return "passwordsNotMatch"
|
|
101
101
|
})
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
-
const [passwordExists, emails] = await Promise.all([
|
|
104
|
+
const [passwordExists, emails, phones] = await Promise.all([
|
|
105
105
|
live(path().passwordAuthentication.myUserPasswordAuthenticationExists()),
|
|
106
|
-
live(path().email
|
|
106
|
+
live(path().email?.myUserEmails()),
|
|
107
|
+
live(path().phone?.myUserPhones())
|
|
107
108
|
])
|
|
108
109
|
|
|
109
|
-
const needPassword = computed(() => (!passwordExists.value
|
|
110
|
+
const needPassword = computed(() => (!passwordExists.value
|
|
111
|
+
&& (emails.value?.length > 0 || phones.value?.length > 0)
|
|
112
|
+
))
|
|
110
113
|
|
|
111
114
|
function handleDone({ parameters, result }) {
|
|
112
115
|
console.log("FORM DONE", parameters, result)
|
package/front/src/sign/routes.js
CHANGED
|
@@ -3,20 +3,25 @@ export function routes(config = {}) {
|
|
|
3
3
|
|
|
4
4
|
return [
|
|
5
5
|
|
|
6
|
-
route({ name: 'user:
|
|
7
|
-
component: () => import("./
|
|
6
|
+
route({ name: 'user:googleAuth', path: prefix + 'google-auth/:action',
|
|
7
|
+
component: () => import("./GoogleAuth.vue"), props: true, meta: { signedOut: true } }),
|
|
8
|
+
route({ name: 'user:googleAuthReturn', path: prefix + 'google-auth-return/:action',
|
|
9
|
+
component: () => import("./GoogleAuthReturn.vue"), props: true }),
|
|
10
|
+
|
|
11
|
+
route({ name: 'user:signInEmail', path: prefix + 'sign-in-email',
|
|
12
|
+
component: () => import("./SignInEmail.vue"), meta: { signedOut: true } }),
|
|
8
13
|
route({ name: 'user:signInFinished', path: prefix + 'sign-in-finished',
|
|
9
14
|
component: () => import("./SignInFinished.vue"), meta: { signedIn: true } }),
|
|
10
15
|
|
|
11
|
-
route({ name: 'user:
|
|
12
|
-
component: () => import("./
|
|
16
|
+
route({ name: 'user:signUpEmail', path: prefix + 'sign-up-email',
|
|
17
|
+
component: () => import("./SignUpEmail.vue"), meta: { signedOut: true } }),
|
|
13
18
|
route({ name: 'user:signUpFinished', path: prefix + 'sign-up-finished',
|
|
14
19
|
component: () => import("./SignUpFinished.vue"), meta: { signedIn: true } }),
|
|
15
20
|
|
|
16
21
|
route({ name: 'user:signOut', path: prefix + 'sign-out',
|
|
17
22
|
component: () => import("./SignOut.vue") }),
|
|
18
23
|
route({ name: 'user:signOutFinished', path: prefix + 'sign-out-finished',
|
|
19
|
-
component: () => import("./SignOutFinished.vue") }),
|
|
24
|
+
component: () => import("./SignOutFinished.vue"), meta: { signedOut: true } }),
|
|
20
25
|
|
|
21
26
|
]
|
|
22
27
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
let googPromise
|
|
2
|
+
|
|
3
|
+
function loadGoogle() {
|
|
4
|
+
if(googPromise) return googPromise
|
|
5
|
+
let resolved = false
|
|
6
|
+
googPromise = new Promise((resolve, reject) => {
|
|
7
|
+
if (typeof window != 'undefined') {
|
|
8
|
+
window.googAsyncInit = function () {
|
|
9
|
+
if(resolved) return
|
|
10
|
+
resolved = true
|
|
11
|
+
resolve(gapi)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
;(function (d, s, id) {
|
|
15
|
+
const fjs = d.getElementsByTagName(s)[0]
|
|
16
|
+
if (d.getElementById(id)) return
|
|
17
|
+
const js = d.createElement(s)
|
|
18
|
+
js.id = id
|
|
19
|
+
js.src = "https://apis.google.com/js/platform.js?onload=googAsyncInit"
|
|
20
|
+
fjs.parentNode.insertBefore(js, fjs)
|
|
21
|
+
}(document, 'script', 'google-jssdk'))
|
|
22
|
+
} else {
|
|
23
|
+
reject('unavailable')
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
return googPromise
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let googAuth2Promise
|
|
30
|
+
|
|
31
|
+
async function loadGoogleAuth2() {
|
|
32
|
+
if(googAuth2Promise) return googAuth2Promise
|
|
33
|
+
let gapi = await loadGoogle()
|
|
34
|
+
let resolved = false
|
|
35
|
+
googAuth2Promise = new Promise((resolve, reject) => {
|
|
36
|
+
gapi.load('auth2', function() {
|
|
37
|
+
if(resolved) return
|
|
38
|
+
resolved = true
|
|
39
|
+
let client_id = ENV_GOOGLE_CLIENT_ID
|
|
40
|
+
resolve(gapi.auth2.init({
|
|
41
|
+
client_id
|
|
42
|
+
}))
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
return googAuth2Promise
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export { loadGoogle, loadGoogleAuth2 }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/user-frontend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.15",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "node --inspect --expose-gc server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; node server/start.js localDev --enableSessions --initScript ./init.js",
|
|
@@ -17,33 +17,34 @@
|
|
|
17
17
|
"build:client": "cd front; vite build --ssrManifest --outDir dist/client",
|
|
18
18
|
"build:server": "cd front; vite build --ssr src/entry-server.js --outDir dist/server",
|
|
19
19
|
"generate": "vite build --ssrManifest --outDir dist/static && yarn build:server && node prerender",
|
|
20
|
-
"debug": "node --inspect-brk server"
|
|
20
|
+
"debug": "node --inspect-brk server",
|
|
21
|
+
"describe": "node server/start.js describe"
|
|
21
22
|
},
|
|
22
23
|
"type": "module",
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@live-change/cli": "^0.8.
|
|
25
|
-
"@live-change/dao": "^0.8.
|
|
26
|
-
"@live-change/dao-vue3": "^0.8.
|
|
27
|
-
"@live-change/dao-websocket": "^0.8.
|
|
28
|
-
"@live-change/email-service": "^0.8.
|
|
29
|
-
"@live-change/framework": "^0.8.
|
|
30
|
-
"@live-change/identicon-service": "^0.8.
|
|
31
|
-
"@live-change/image-frontend": "^0.8.
|
|
32
|
-
"@live-change/message-authentication-service": "^0.8.
|
|
33
|
-
"@live-change/notification-service": "^0.8.
|
|
34
|
-
"@live-change/password-authentication-service": "^0.8.
|
|
35
|
-
"@live-change/pattern": "^0.8.
|
|
36
|
-
"@live-change/secret-code-service": "^0.8.
|
|
37
|
-
"@live-change/secret-link-service": "^0.8.
|
|
38
|
-
"@live-change/security-frontend": "^0.8.
|
|
39
|
-
"@live-change/security-service": "^0.8.
|
|
40
|
-
"@live-change/session-service": "^0.8.
|
|
41
|
-
"@live-change/timer-service": "^0.8.
|
|
42
|
-
"@live-change/upload-service": "^0.8.
|
|
43
|
-
"@live-change/user-identification-service": "^0.8.
|
|
44
|
-
"@live-change/user-service": "^0.8.
|
|
45
|
-
"@live-change/vue3-components": "^0.8.
|
|
46
|
-
"@live-change/vue3-ssr": "^0.8.
|
|
25
|
+
"@live-change/cli": "^0.8.15",
|
|
26
|
+
"@live-change/dao": "^0.8.15",
|
|
27
|
+
"@live-change/dao-vue3": "^0.8.15",
|
|
28
|
+
"@live-change/dao-websocket": "^0.8.15",
|
|
29
|
+
"@live-change/email-service": "^0.8.15",
|
|
30
|
+
"@live-change/framework": "^0.8.15",
|
|
31
|
+
"@live-change/identicon-service": "^0.8.15",
|
|
32
|
+
"@live-change/image-frontend": "^0.8.15",
|
|
33
|
+
"@live-change/message-authentication-service": "^0.8.15",
|
|
34
|
+
"@live-change/notification-service": "^0.8.15",
|
|
35
|
+
"@live-change/password-authentication-service": "^0.8.15",
|
|
36
|
+
"@live-change/pattern": "^0.8.15",
|
|
37
|
+
"@live-change/secret-code-service": "^0.8.15",
|
|
38
|
+
"@live-change/secret-link-service": "^0.8.15",
|
|
39
|
+
"@live-change/security-frontend": "^0.8.15",
|
|
40
|
+
"@live-change/security-service": "^0.8.15",
|
|
41
|
+
"@live-change/session-service": "^0.8.15",
|
|
42
|
+
"@live-change/timer-service": "^0.8.15",
|
|
43
|
+
"@live-change/upload-service": "^0.8.15",
|
|
44
|
+
"@live-change/user-identification-service": "^0.8.15",
|
|
45
|
+
"@live-change/user-service": "^0.8.15",
|
|
46
|
+
"@live-change/vue3-components": "^0.8.15",
|
|
47
|
+
"@live-change/vue3-ssr": "^0.8.15",
|
|
47
48
|
"@vueuse/core": "^10.7.2",
|
|
48
49
|
"codeceptjs-assert": "^0.0.5",
|
|
49
50
|
"codeceptjs-video-helper": "0.1.3",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"wtfnode": "^0.9.1"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"@live-change/codeceptjs-helper": "^0.8.
|
|
68
|
+
"@live-change/codeceptjs-helper": "^0.8.15",
|
|
68
69
|
"codeceptjs": "^3.5.12",
|
|
69
70
|
"generate-password": "1.7.1",
|
|
70
71
|
"playwright": "^1.41.2",
|
|
@@ -75,5 +76,5 @@
|
|
|
75
76
|
"author": "",
|
|
76
77
|
"license": "BSD-3-Clause",
|
|
77
78
|
"description": "",
|
|
78
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "4897eefbf3ce9ace57630127da89503c71114563"
|
|
79
80
|
}
|
package/server/app.config.js
CHANGED
|
@@ -2,6 +2,7 @@ import App from "@live-change/framework"
|
|
|
2
2
|
const app = App.app()
|
|
3
3
|
|
|
4
4
|
const contactTypes = ['email', 'phone']
|
|
5
|
+
const remoteAccountTypes = ['google']
|
|
5
6
|
|
|
6
7
|
import securityConfig from './security.config.js'
|
|
7
8
|
|
|
@@ -18,7 +19,8 @@ app.config = {
|
|
|
18
19
|
},
|
|
19
20
|
{
|
|
20
21
|
name: 'user',
|
|
21
|
-
path: '@live-change/user-service'
|
|
22
|
+
path: '@live-change/user-service',
|
|
23
|
+
remoteAccountTypes
|
|
22
24
|
},
|
|
23
25
|
{
|
|
24
26
|
name: 'email',
|
|
@@ -50,6 +52,10 @@ app.config = {
|
|
|
50
52
|
contactTypes,
|
|
51
53
|
signInWithoutPassword: true
|
|
52
54
|
},
|
|
55
|
+
{
|
|
56
|
+
name: 'googleAuthentication',
|
|
57
|
+
path: '@live-change/google-authentication-service',
|
|
58
|
+
},
|
|
53
59
|
{
|
|
54
60
|
name: 'security',
|
|
55
61
|
path: '@live-change/security-service',
|
package/server/services.list.js
CHANGED
|
@@ -13,6 +13,7 @@ import image from '@live-change/image-service'
|
|
|
13
13
|
import secretCode from '@live-change/secret-code-service'
|
|
14
14
|
import secretLink from '@live-change/secret-link-service'
|
|
15
15
|
import messageAuthentication from '@live-change/message-authentication-service'
|
|
16
|
+
import googleAuthentication from '@live-change/google-authentication-service'
|
|
16
17
|
|
|
17
18
|
import backup from '@live-change/backup-service'
|
|
18
19
|
import init from './init.js'
|
|
@@ -32,6 +33,7 @@ export {
|
|
|
32
33
|
secretCode,
|
|
33
34
|
secretLink,
|
|
34
35
|
messageAuthentication,
|
|
36
|
+
googleAuthentication,
|
|
35
37
|
localeSettings,
|
|
36
38
|
backup,
|
|
37
39
|
init
|