Skip to content

Commit e823301

Browse files
authoredFeb 21, 2024··
Add ability to sign in via SSO (#73)
* Add ability to sign in via SSO * Remove unused commit in store action
1 parent 443ce03 commit e823301

File tree

4 files changed

+117
-13
lines changed

4 files changed

+117
-13
lines changed
 

‎server/ui/src/App.vue

+14-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,23 @@
66
</template>
77

88
<script>
9+
import { mapState } from 'vuex'
910
import UploadManager from '@/components/UploadManager'
1011
1112
export default {
12-
components: { UploadManager }
13+
components: { UploadManager },
14+
15+
computed: mapState(['unique']),
16+
17+
created() {
18+
const { query, path } = this.$route
19+
if (!this.unique && query.auth) {
20+
const data = { token: query.auth }
21+
this.$store.commit('PATCH_USER', data)
22+
this.$store.dispatch('refresh')
23+
this.$router.replace({ path })
24+
}
25+
}
1326
}
1427
</script>
1528

‎server/ui/src/api.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import store from '@/store'
2-
const LOGIN_API = 'https://api.nanome.ai/user'
2+
const AUTH_API = 'https://api.nanome.ai'
33

44
function replacePath(path) {
55
return path
@@ -13,7 +13,7 @@ function addSlash(path) {
1313
}
1414

1515
async function request(url, options = {}) {
16-
if (store.state.token && !url.startsWith(LOGIN_API)) {
16+
if (store.state.token && !url.startsWith(AUTH_API)) {
1717
options.headers = Object.assign({}, options.headers, {
1818
Authorization: 'Bearer ' + store.state.token
1919
})
@@ -82,18 +82,31 @@ const API = {
8282
source: 'web:plugin-vault'
8383
}
8484

85-
return request(`${LOGIN_API}/login`, {
85+
return request(`${AUTH_API}/user/login`, {
8686
headers: { 'Content-Type': 'application/json' },
8787
method: 'POST',
8888
body: JSON.stringify(body)
8989
})
9090
},
9191

92+
loginSSO(email, redirect) {
93+
const params = new URLSearchParams({
94+
email,
95+
redirect,
96+
cookie: false
97+
})
98+
99+
return request(`${AUTH_API}/sso/login?` + params, {
100+
headers: { 'Content-Type': 'application/json' },
101+
method: 'GET'
102+
})
103+
},
104+
92105
refresh() {
93106
const token = store.state.token
94107
if (!token) return {}
95108

96-
return request(`${LOGIN_API}/session`, {
109+
return request(`${AUTH_API}/user/session`, {
97110
headers: { Authorization: `Bearer ${token}` }
98111
})
99112
},

‎server/ui/src/components/Modal.vue

+77-7
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,60 @@
2929
type="password"
3030
/>
3131

32-
<p v-if="error" class="text-red-500">incorrect login or password</p>
32+
<p v-if="error" class="text-red-500">{{ error }}</p>
3333

3434
<p>
3535
<a
36+
key="login-forgot"
3637
href="https://home.nanome.ai/login/forgot"
3738
target="_blank"
3839
class="link text-blue-500"
40+
>forgot password?</a
3941
>
40-
forgot password?
42+
or
43+
44+
<a
45+
key="login-sso"
46+
class="link text-blue-500"
47+
@click.stop="options.type = 'login-sso'"
48+
>log in with SSO</a
49+
>
50+
</p>
51+
52+
<p></p>
53+
54+
<p class="text-xs text-gray-700">
55+
Don't have a Nanome account?
56+
<a
57+
href="https://home.nanome.ai/register"
58+
target="_blank"
59+
class="link text-blue-500"
60+
>
61+
Register here
62+
</a>
63+
</p>
64+
</template>
65+
66+
<template v-else-if="options.type === 'login-sso'">
67+
<input
68+
ref="login"
69+
v-model="input1"
70+
:class="{ 'border-red-500': error }"
71+
placeholder="email"
72+
type="text"
73+
/>
74+
75+
<p v-if="error" class="text-red-500">
76+
{{ error }}
77+
</p>
78+
79+
<p>
80+
<a
81+
key="login-username"
82+
class="link text-blue-500"
83+
@click.stop="options.type = 'login'"
84+
>
85+
log in with username
4186
</a>
4287
</p>
4388

@@ -107,7 +152,7 @@ const deferred = () => {
107152
export default {
108153
data: () => ({
109154
showing: false,
110-
error: false,
155+
error: null,
111156
loading: false,
112157
options: { ...defaults },
113158
input1: '',
@@ -121,14 +166,22 @@ export default {
121166
122167
if (this.options.type === 'login') {
123168
return !this.input1 || !this.input2
124-
} else if (this.options.type === 'prompt') {
169+
} else if (['login-sso', 'prompt'].includes(this.options.type)) {
125170
return !this.input1
126171
}
127172
128173
return false
129174
}
130175
},
131176
177+
watch: {
178+
'options.type'() {
179+
this.error = null
180+
this.input1 = ''
181+
this.input2 = ''
182+
}
183+
},
184+
132185
mounted() {
133186
document.body.addEventListener('keydown', this.onKeydown)
134187
},
@@ -229,6 +282,9 @@ export default {
229282
} else if (this.options.type === 'login') {
230283
this.attemptLogin()
231284
return
285+
} else if (this.options.type === 'login-sso') {
286+
this.attemptLoginSSO()
287+
return
232288
}
233289
234290
this.deferred.resolve(data)
@@ -237,7 +293,7 @@ export default {
237293
238294
async attemptLogin() {
239295
const deferred = this.deferred
240-
this.error = false
296+
this.error = null
241297
242298
const creds = {
243299
username: this.input1,
@@ -252,6 +308,9 @@ export default {
252308
break
253309
} catch (e) {
254310
this.loading = false
311+
this.error = e.message
312+
if (!this.error.includes('2FA')) break
313+
255314
creds.tfa_code = await this.prompt({
256315
title: 'Enter 2FA code',
257316
body: 'Or use a one time recovery code',
@@ -269,8 +328,19 @@ export default {
269328
if (success) {
270329
deferred.resolve(true)
271330
this.reset()
272-
} else {
273-
this.error = true
331+
}
332+
},
333+
334+
async attemptLoginSSO() {
335+
this.error = null
336+
this.loading = true
337+
338+
try {
339+
const url = await this.$store.dispatch('loginSSO', this.input1)
340+
window.open(url, '_self')
341+
} catch (e) {
342+
this.loading = false
343+
this.error = e.message
274344
}
275345
}
276346
}

‎server/ui/src/store.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,20 @@ const actions = {
7575

7676
async login({ commit }, creds) {
7777
const data = await API.login(creds)
78-
if (data.code === 401) {
78+
if (data.code !== 200) {
7979
throw new Error(data.error.message)
8080
}
8181
return saveSession(commit, data)
8282
},
8383

84+
async loginSSO(_, email) {
85+
const data = await API.loginSSO(email, window.location.href)
86+
if (data.code !== 200) {
87+
throw new Error(data.error.message)
88+
}
89+
return data.results
90+
},
91+
8492
async refresh({ commit }) {
8593
const data = await API.refresh()
8694
return saveSession(commit, data)

0 commit comments

Comments
 (0)