import {createStore} from 'vuex'; import router from '@/router'; import FallBackResolver from "@/dns"; import NeighborsCache from "@/neigbors"; export default createStore({ state: { user: null, token: null, keypair: null, remember: false, friends: [], item_map: {}, //notifications: [], messages: [], home_server: null, resolver: new FallBackResolver(), unreachable_neighbors: new NeighborsCache(), }, mutations: { setUser(state, user) { state.user = user; if (state.remember) localStorage.setItem('user', user); }, setToken(state, token) { state.token = token; if (state.remember) localStorage.setItem('token', token); }, setKey(state, keypair) { state.keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(keypair)) if (state.remember) localStorage.setItem('keypair', nacl.to_hex(state.keypair.signSk).slice(0, 64)) }, setRemember(state, remember) { state.remember = remember; if (!remember) { localStorage.removeItem('user'); localStorage.removeItem('token'); localStorage.removeItem('keypair'); } localStorage.setItem('remember', remember); }, setInventoryItems(state, {url, items}) { state.item_map[url] = items; }, logout(state) { state.user = null; state.token = null; state.keypair = null; localStorage.removeItem('user'); localStorage.removeItem('token'); localStorage.removeItem('keypair'); router.push('/login'); }, init(state) { const remember = localStorage.getItem('remember'); const user = localStorage.getItem('user'); const token = localStorage.getItem('token'); const keypair = localStorage.getItem('keypair'); if (user && token) { this.commit('setUser', user); this.commit('setToken', token); if (keypair) { this.commit('setKey', keypair) } else { } router.push('/'); /*if (this.$route.query.redirect) { router.push({path: this.$route.query.redirect}); } else { router.push({path: '/'}); }*/ } } }, actions: { async login({commit, dispatch, state}, {username, password, remember}) { commit('setRemember', remember); const data = await dispatch('apiLocalPost', { target: '/auth/token/', data: { username: username, password: password } }) if (data.token) { commit('setToken', data.token); commit('setUser', username); const j = await dispatch('apiLocalGet', {target: '/auth/keys/'}) const k = j.key commit('setKey', k) return true; } else { return false; } }, async getFriends(state) { return ['jedi@j3d1.de', 'foobar@example.com', 'foobaz@example.eleon', 'alice@example2.com']; }, async getFriendServer({state}, {username}) { const domain = username.split('@')[1] if (domain === 'example.eleon') return ['10.23.42.186:8000']; if (domain === 'localhost') return ['127.0.0.1:8000']; if (domain === 'example.com') return ['10.23.42.128:8000']; if (domain === 'example.jedi') return ['10.23.42.128:8000']; if (domain === 'example2.com') return ['10.23.42.128:9000']; const request = '_toolshed-server._tcp.' + domain + '.' return await state.resolver.query(request, 'SRV').then( (result) => result.map( (answer) => answer.target + ':' + answer.port)) }, async apiFederatedGet({state}, {host, target}) { if (state.unreachable_neighbors.queryUnreachable(host)) { throw new Error('unreachable neighbor') } if (!state.user || !state.keypair) { throw new Error('no user or keypair') } const url = "http://" + host + target // TODO https const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url), state.keypair.signSk) const auth = 'Signature ' + state.user + ':' + nacl.to_hex(signature) return await fetch(url, { method: 'GET', headers: { 'Authorization': auth } }).catch(err => state.unreachable_neighbors.unreachable(host) ).then(response => response.json()) }, async apiFederatedPost({state}, {host, target, data}) { console.log('apiFederatedPost', host, target, data) if (state.unreachable_neighbors.queryUnreachable(host)) { throw new Error('unreachable neighbor') } if (!state.user || !state.keypair) { throw new Error('no user or keypair') } const url = "http://" + host + target // TODO https const json = JSON.stringify(data) const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url + json), state.keypair.signSk) const auth = 'Signature ' + state.user + ':' + nacl.to_hex(signature) return await fetch(url, { method: 'POST', headers: { 'Authorization': auth, 'Content-Type': 'application/json' }, body: json }).catch(err => state.unreachable_neighbors.unreachable(host) ).then(response => response.json()) }, async apiLocalGet({state}, {target}) { const auth = state.token ? {'Authorization': 'Token ' + state.token} : {} return await fetch(target, { method: 'GET', headers: auth, credentials: 'omit' }).then(response => response.json()) }, async apiLocalPost({state}, {target, data}) { const auth = state.token ? {'Authorization': 'Token ' + state.token} : {} return await fetch(target, { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth }, credentials: 'omit', body: JSON.stringify(data) }).then(response => response.json()) }, async requestFriend({state, dispatch}, {username}) { console.log('requesting friend ' + username) if (username in state.friends) { return true; } state.home_server = 'localhost:8000' const home_reply = await dispatch('apiFederatedPost', { host: state.home_server, target: '/api/friendrequests/', data: {befriender: state.user, befriendee: username} }) if (home_reply.status !== 'pending' || !home_reply.secret) return false; console.log('home_reply', home_reply) const befriendee_server = await dispatch('getFriendServer', {username}) const ext_reply = await dispatch('apiFederatedPost', { host: befriendee_server[0], target: '/api/friendrequests/', data: { befriender: state.user, befriendee: username, befriender_key: nacl.to_hex(state.keypair.signPk), secret: home_reply.secret } }) console.log('ext_reply', ext_reply) return true; }, async acceptFriend({state, dispatch}, args) { console.log('accepting friend ' + args) }, async declineFriend({state, dispatch}, args) { console.log('declining friend ' + args) }, async fetchFriendRequests({state, dispatch}) { const requests = await dispatch('apiLocalGet', {target: '/api/friendrequests/'}) return requests } }, getters: { isLoggedIn(state) { return state.user !== null && state.token !== null; }, inventory_items(state) { return Object.entries(state.item_map).reduce((acc, [url, items]) => { return acc.concat(items) }, []) }, notifications(state) { // supported types: error, warning, info, login, success, friend const u = state.unreachable_neighbors.list().map(elem => { return { type: 'error', title: elem.domain + ' unreachable', msg: 'The neighbor ' + elem.domain + ' is currently unreachable. Please try again later.', time: elem.time } }) return [...u, { type: 'info', title: 'Welcome to the Toolshed', msg: 'This is a federated social network. You can add friends from other servers and share items with them.', time: Date.now() }, { type: 'warning', title: 'Lorem ipsum', msg: 'Aliquam ex eros, imperdiet vulputate hendrerit et.', time: Date.now() - 1000 * 60 * 60 * 2 }, { type: 'login', title: 'Login from 192.186.1.8', time: Date.now() - 1000 * 60 * 60 * 5 }, { type: 'friend', title: 'New connection', msg: 'Christina accepted your request.', time: Date.now() - 1000 * 60 * 60 * 14 }, { type: 'success', title: 'Lorem ipsum', msg: 'Aliquam ex eros, imperdiet vulputate hendrerit et.', time: Date.now() - 1000 * 60 * 60 * 24 }] }, } })