2021-07-25 11:49:15 +02:00
|
|
|
import {HTTPFactory} from '@/http-common'
|
2021-10-17 21:38:30 +02:00
|
|
|
import {LOADING} from '../mutation-types'
|
2020-09-05 22:35:52 +02:00
|
|
|
import UserModel from '../../models/user'
|
2021-07-09 20:10:57 +02:00
|
|
|
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
2020-05-08 20:43:51 +02:00
|
|
|
|
2021-09-10 15:04:00 +02:00
|
|
|
const AUTH_TYPES = {
|
|
|
|
'UNKNOWN': 0,
|
|
|
|
'USER': 1,
|
|
|
|
'LINK_SHARE': 2,
|
|
|
|
}
|
|
|
|
|
2021-06-03 18:12:40 +02:00
|
|
|
const defaultSettings = settings => {
|
|
|
|
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
|
|
|
|
settings.weekStart = 0
|
|
|
|
}
|
|
|
|
return settings
|
|
|
|
}
|
|
|
|
|
2020-05-08 20:43:51 +02:00
|
|
|
export default {
|
|
|
|
namespaced: true,
|
|
|
|
state: () => ({
|
|
|
|
authenticated: false,
|
|
|
|
isLinkShareAuth: false,
|
2021-04-07 15:58:29 +02:00
|
|
|
info: null,
|
2020-05-08 20:43:51 +02:00
|
|
|
needsTotpPasscode: false,
|
2020-08-02 19:17:29 +02:00
|
|
|
avatarUrl: '',
|
2021-04-07 15:58:29 +02:00
|
|
|
lastUserInfoRefresh: null,
|
2021-04-07 21:31:14 +02:00
|
|
|
settings: {},
|
2020-05-08 20:43:51 +02:00
|
|
|
}),
|
2021-09-10 15:04:00 +02:00
|
|
|
getters: {
|
|
|
|
authUser(state) {
|
|
|
|
return state.authenticated && (
|
|
|
|
state.info &&
|
|
|
|
state.info.type === AUTH_TYPES.USER
|
|
|
|
)
|
|
|
|
},
|
|
|
|
authLinkShare(state) {
|
|
|
|
return state.authenticated && (
|
|
|
|
state.info &&
|
|
|
|
state.info.type === AUTH_TYPES.LINK_SHARE
|
|
|
|
)
|
|
|
|
},
|
|
|
|
},
|
2020-05-08 20:43:51 +02:00
|
|
|
mutations: {
|
|
|
|
info(state, info) {
|
|
|
|
state.info = info
|
2021-04-07 15:58:29 +02:00
|
|
|
if (info !== null) {
|
|
|
|
state.avatarUrl = info.getAvatarUrl()
|
2021-04-09 16:29:07 +02:00
|
|
|
|
|
|
|
if (info.settings) {
|
2021-06-03 18:12:40 +02:00
|
|
|
state.settings = defaultSettings(info.settings)
|
2021-04-09 16:29:07 +02:00
|
|
|
}
|
2021-07-25 11:49:15 +02:00
|
|
|
|
|
|
|
state.isLinkShareAuth = info.id < 0
|
2021-04-07 21:31:14 +02:00
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
2021-04-07 21:31:14 +02:00
|
|
|
setUserSettings(state, settings) {
|
2021-06-03 18:12:40 +02:00
|
|
|
state.settings = defaultSettings(settings)
|
2021-04-07 21:31:14 +02:00
|
|
|
const info = state.info !== null ? state.info : {}
|
|
|
|
info.name = settings.name
|
|
|
|
state.info = info
|
2020-11-21 22:25:00 +01:00
|
|
|
},
|
2020-05-08 20:43:51 +02:00
|
|
|
authenticated(state, authenticated) {
|
|
|
|
state.authenticated = authenticated
|
|
|
|
},
|
|
|
|
isLinkShareAuth(state, is) {
|
|
|
|
state.isLinkShareAuth = is
|
|
|
|
},
|
|
|
|
needsTotpPasscode(state, needs) {
|
|
|
|
state.needsTotpPasscode = needs
|
|
|
|
},
|
2020-08-02 19:17:29 +02:00
|
|
|
reloadAvatar(state) {
|
|
|
|
state.avatarUrl = `${state.info.getAvatarUrl()}&=${+new Date()}`
|
|
|
|
},
|
2021-04-07 15:58:29 +02:00
|
|
|
lastUserRefresh(state) {
|
|
|
|
state.lastUserInfoRefresh = new Date()
|
|
|
|
},
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
|
|
|
actions: {
|
|
|
|
// Logs a user in with a set of credentials.
|
2021-10-11 19:37:20 +02:00
|
|
|
async login(ctx, credentials) {
|
2020-10-11 12:13:35 +02:00
|
|
|
const HTTP = HTTPFactory()
|
2021-07-25 11:49:15 +02:00
|
|
|
ctx.commit(LOADING, true, {root: true})
|
2020-05-08 20:43:51 +02:00
|
|
|
|
|
|
|
// Delete an eventually preexisting old token
|
2021-07-09 20:10:57 +02:00
|
|
|
removeToken()
|
2020-05-08 20:43:51 +02:00
|
|
|
|
2020-09-05 22:35:52 +02:00
|
|
|
const data = {
|
2020-05-08 20:43:51 +02:00
|
|
|
username: credentials.username,
|
2020-09-05 22:35:52 +02:00
|
|
|
password: credentials.password,
|
2020-05-08 20:43:51 +02:00
|
|
|
}
|
|
|
|
|
2020-09-05 22:35:52 +02:00
|
|
|
if (credentials.totpPasscode) {
|
2020-05-08 20:43:51 +02:00
|
|
|
data.totp_passcode = credentials.totpPasscode
|
|
|
|
}
|
|
|
|
|
2021-10-11 19:37:20 +02:00
|
|
|
try {
|
|
|
|
const response = await HTTP.post('login', data)
|
|
|
|
// Save the token to local storage for later use
|
|
|
|
saveToken(response.data.token, true)
|
|
|
|
|
|
|
|
// Tell others the user is autheticated
|
|
|
|
ctx.dispatch('checkAuth')
|
|
|
|
} catch(e) {
|
|
|
|
if (
|
|
|
|
e.response &&
|
|
|
|
e.response.data.code === 1017 &&
|
|
|
|
!credentials.totpPasscode
|
|
|
|
) {
|
|
|
|
ctx.commit('needsTotpPasscode', true)
|
|
|
|
}
|
2021-04-09 16:29:07 +02:00
|
|
|
|
2021-10-11 19:37:20 +02:00
|
|
|
throw e
|
|
|
|
} finally {
|
|
|
|
ctx.commit(LOADING, false, {root: true})
|
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
2020-05-08 20:43:51 +02:00
|
|
|
// Registers a new user and logs them in.
|
|
|
|
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
|
2021-10-11 19:37:20 +02:00
|
|
|
async register(ctx, credentials) {
|
2020-10-11 12:13:35 +02:00
|
|
|
const HTTP = HTTPFactory()
|
2021-10-11 19:37:20 +02:00
|
|
|
try {
|
|
|
|
await HTTP.post('register', {
|
|
|
|
username: credentials.username,
|
|
|
|
email: credentials.email,
|
|
|
|
password: credentials.password,
|
2020-05-08 20:43:51 +02:00
|
|
|
})
|
2021-10-11 19:37:20 +02:00
|
|
|
return ctx.dispatch('login', credentials)
|
|
|
|
} catch(e) {
|
2021-10-17 21:38:30 +02:00
|
|
|
if (e.response?.data?.message) {
|
|
|
|
throw e.response.data
|
2021-10-11 19:37:20 +02:00
|
|
|
}
|
2021-04-09 16:29:07 +02:00
|
|
|
|
2021-10-11 19:37:20 +02:00
|
|
|
throw e
|
|
|
|
} finally {
|
|
|
|
ctx.commit(LOADING, false, {root: true})
|
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
|
|
|
async openIdAuth(ctx, {provider, code}) {
|
2020-11-21 17:38:40 +01:00
|
|
|
const HTTP = HTTPFactory()
|
2021-07-25 11:49:15 +02:00
|
|
|
ctx.commit(LOADING, true, {root: true})
|
2020-11-21 17:38:40 +01:00
|
|
|
|
|
|
|
const data = {
|
|
|
|
code: code,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete an eventually preexisting old token
|
2021-07-09 20:10:57 +02:00
|
|
|
removeToken()
|
2021-10-11 19:37:20 +02:00
|
|
|
try {
|
|
|
|
const response = await HTTP.post(`/auth/openid/${provider}/callback`, data)
|
|
|
|
// Save the token to local storage for later use
|
|
|
|
saveToken(response.data.token, true)
|
|
|
|
|
|
|
|
// Tell others the user is autheticated
|
|
|
|
ctx.dispatch('checkAuth')
|
|
|
|
} finally {
|
|
|
|
ctx.commit(LOADING, false, {root: true})
|
|
|
|
}
|
2020-11-21 17:38:40 +01:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
|
|
|
async linkShareAuth(ctx, {hash, password}) {
|
2020-10-11 12:13:35 +02:00
|
|
|
const HTTP = HTTPFactory()
|
2021-10-11 19:37:20 +02:00
|
|
|
const response = await HTTP.post('/shares/' + hash + '/auth', {
|
2021-04-11 15:18:19 +02:00
|
|
|
password: password,
|
|
|
|
})
|
2021-10-11 19:37:20 +02:00
|
|
|
saveToken(response.data.token, false)
|
|
|
|
ctx.dispatch('checkAuth')
|
|
|
|
return response.data
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
2020-05-08 20:43:51 +02:00
|
|
|
// Populates user information from jwt token saved in local storage in store
|
|
|
|
checkAuth(ctx) {
|
2021-04-07 15:58:29 +02:00
|
|
|
|
|
|
|
// This function can be called from multiple places at the same time and shortly after one another.
|
|
|
|
// To prevent hitting the api too frequently or race conditions, we check at most once per minute.
|
|
|
|
if (ctx.state.lastUserInfoRefresh !== null && ctx.state.lastUserInfoRefresh > (new Date()).setMinutes((new Date()).getMinutes() + 1)) {
|
2021-10-09 16:34:57 +02:00
|
|
|
return
|
2021-04-07 15:58:29 +02:00
|
|
|
}
|
|
|
|
|
2021-07-09 20:10:57 +02:00
|
|
|
const jwt = getToken()
|
2020-05-08 20:43:51 +02:00
|
|
|
let authenticated = false
|
|
|
|
if (jwt) {
|
|
|
|
const base64 = jwt
|
|
|
|
.split('.')[1]
|
|
|
|
.replace('-', '+')
|
|
|
|
.replace('_', '/')
|
2021-10-09 16:34:57 +02:00
|
|
|
const info = new UserModel(JSON.parse(atob(base64)))
|
2020-05-08 20:43:51 +02:00
|
|
|
const ts = Math.round((new Date()).getTime() / 1000)
|
2021-04-07 15:58:29 +02:00
|
|
|
authenticated = info.exp >= ts
|
2020-05-08 20:43:51 +02:00
|
|
|
ctx.commit('info', info)
|
2021-04-07 15:58:29 +02:00
|
|
|
|
2021-04-07 21:31:14 +02:00
|
|
|
if (authenticated) {
|
2021-08-11 21:08:18 +02:00
|
|
|
ctx.dispatch('refreshUserInfo')
|
2021-04-07 15:58:29 +02:00
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
}
|
2021-04-07 15:58:29 +02:00
|
|
|
|
2020-05-08 20:43:51 +02:00
|
|
|
ctx.commit('authenticated', authenticated)
|
2021-04-07 15:58:29 +02:00
|
|
|
if (!authenticated) {
|
|
|
|
ctx.commit('info', null)
|
2021-08-15 12:02:29 +02:00
|
|
|
ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
|
2021-04-07 15:58:29 +02:00
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
|
|
|
async refreshUserInfo(ctx) {
|
2021-08-11 21:08:18 +02:00
|
|
|
const jwt = getToken()
|
|
|
|
if (!jwt) {
|
|
|
|
return
|
|
|
|
}
|
2021-08-15 12:02:29 +02:00
|
|
|
|
2021-08-11 21:08:18 +02:00
|
|
|
const HTTP = HTTPFactory()
|
2021-10-11 19:37:20 +02:00
|
|
|
try {
|
2021-08-11 21:08:18 +02:00
|
|
|
|
2021-10-11 19:37:20 +02:00
|
|
|
const response = await HTTP.get('user', {
|
|
|
|
headers: {
|
|
|
|
Authorization: `Bearer ${jwt}`,
|
|
|
|
},
|
2021-08-11 21:08:18 +02:00
|
|
|
})
|
2021-10-11 19:37:20 +02:00
|
|
|
const info = new UserModel(response.data)
|
|
|
|
info.type = ctx.state.info.type
|
|
|
|
info.email = ctx.state.info.email
|
|
|
|
info.exp = ctx.state.info.exp
|
|
|
|
|
|
|
|
ctx.commit('info', info)
|
|
|
|
ctx.commit('lastUserRefresh')
|
|
|
|
return info
|
|
|
|
} catch(e) {
|
|
|
|
throw new Error('Error while refreshing user info:', { cause: e })
|
|
|
|
}
|
2021-08-11 21:08:18 +02:00
|
|
|
},
|
2021-10-11 19:37:20 +02:00
|
|
|
|
2020-05-08 20:43:51 +02:00
|
|
|
// Renews the api token and saves it to local storage
|
|
|
|
renewToken(ctx) {
|
2021-10-11 19:37:20 +02:00
|
|
|
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
|
2021-07-10 12:32:04 +02:00
|
|
|
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
|
|
|
|
// the same time and one might win over the other.
|
2021-10-11 19:37:20 +02:00
|
|
|
setTimeout(async () => {
|
2021-07-10 12:32:04 +02:00
|
|
|
if (!ctx.state.authenticated) {
|
|
|
|
return
|
|
|
|
}
|
2020-05-08 20:43:51 +02:00
|
|
|
|
2021-10-11 19:37:20 +02:00
|
|
|
try {
|
|
|
|
await refreshToken(!ctx.state.isLinkShareAuth)
|
|
|
|
ctx.dispatch('checkAuth')
|
|
|
|
} catch(e) {
|
|
|
|
// Don't logout on network errors as the user would then get logged out if they don't have
|
|
|
|
// internet for a short period of time - such as when the laptop is still reconnecting
|
|
|
|
if (e.request.status) {
|
|
|
|
ctx.dispatch('logout')
|
|
|
|
}
|
|
|
|
}
|
2021-07-10 12:32:04 +02:00
|
|
|
}, 5000)
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
|
|
|
logout(ctx) {
|
2021-07-09 20:10:57 +02:00
|
|
|
removeToken()
|
2020-05-08 20:43:51 +02:00
|
|
|
ctx.dispatch('checkAuth')
|
2020-09-05 22:35:52 +02:00
|
|
|
},
|
2020-05-08 20:43:51 +02:00
|
|
|
},
|
|
|
|
}
|