diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.js | 1 | ||||
| -rw-r--r-- | src/boot/after_store.js | 176 | ||||
| -rw-r--r-- | src/components/login_form/login_form.js | 34 | ||||
| -rw-r--r-- | src/components/login_form/login_form.vue | 15 | ||||
| -rw-r--r-- | src/components/oauth_callback/oauth_callback.js | 20 | ||||
| -rw-r--r-- | src/components/oauth_callback/oauth_callback.vue | 5 | ||||
| -rw-r--r-- | src/components/registration/registration.js | 22 | ||||
| -rw-r--r-- | src/i18n/de.json | 1 | ||||
| -rw-r--r-- | src/i18n/en.json | 1 | ||||
| -rw-r--r-- | src/lib/persisted_state.js | 53 | ||||
| -rw-r--r-- | src/main.js | 213 | ||||
| -rw-r--r-- | src/modules/instance.js | 1 | ||||
| -rw-r--r-- | src/modules/oauth.js | 18 | ||||
| -rw-r--r-- | src/modules/users.js | 10 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 16 | ||||
| -rw-r--r-- | src/services/new_api/oauth.js | 82 |
16 files changed, 426 insertions, 242 deletions
@@ -73,6 +73,7 @@ export default { window.scrollTo(0, 0) }, logout () { + this.$router.replace('/main/public') this.$store.dispatch('logout') } } diff --git a/src/boot/after_store.js b/src/boot/after_store.js new file mode 100644 index 00000000..6b8aef7f --- /dev/null +++ b/src/boot/after_store.js @@ -0,0 +1,176 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +import App from '../App.vue' +import PublicTimeline from '../components/public_timeline/public_timeline.vue' +import PublicAndExternalTimeline from '../components/public_and_external_timeline/public_and_external_timeline.vue' +import FriendsTimeline from '../components/friends_timeline/friends_timeline.vue' +import TagTimeline from '../components/tag_timeline/tag_timeline.vue' +import ConversationPage from '../components/conversation-page/conversation-page.vue' +import Mentions from '../components/mentions/mentions.vue' +import UserProfile from '../components/user_profile/user_profile.vue' +import Settings from '../components/settings/settings.vue' +import Registration from '../components/registration/registration.vue' +import UserSettings from '../components/user_settings/user_settings.vue' +import FollowRequests from '../components/follow_requests/follow_requests.vue' +import OAuthCallback from '../components/oauth_callback/oauth_callback.vue' + +const afterStoreSetup = ({store, i18n}) => { + window.fetch('/api/statusnet/config.json') + .then((res) => res.json()) + .then((data) => { + const {name, closed: registrationClosed, textlimit, server} = data.site + + store.dispatch('setInstanceOption', { name: 'name', value: name }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) + store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) + store.dispatch('setInstanceOption', { name: 'server', value: server }) + + var apiConfig = data.site.pleromafe + + window.fetch('/static/config.json') + .then((res) => res.json()) + .catch((err) => { + console.warn('Failed to load static/config.json, continuing without it.') + console.warn(err) + return {} + }) + .then((staticConfig) => { + // This takes static config and overrides properties that are present in apiConfig + var config = Object.assign({}, staticConfig, apiConfig) + + var theme = (config.theme) + var background = (config.background) + var hidePostStats = (config.hidePostStats) + var hideUserStats = (config.hideUserStats) + var logo = (config.logo) + var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask) + var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin) + var redirectRootNoLogin = (config.redirectRootNoLogin) + var redirectRootLogin = (config.redirectRootLogin) + var chatDisabled = (config.chatDisabled) + var showInstanceSpecificPanel = (config.showInstanceSpecificPanel) + var scopeOptionsEnabled = (config.scopeOptionsEnabled) + var formattingOptionsEnabled = (config.formattingOptionsEnabled) + var collapseMessageWithSubject = (config.collapseMessageWithSubject) + var loginMethod = (config.loginMethod) + + store.dispatch('setInstanceOption', { name: 'theme', value: theme }) + store.dispatch('setInstanceOption', { name: 'background', value: background }) + store.dispatch('setInstanceOption', { name: 'hidePostStats', value: hidePostStats }) + store.dispatch('setInstanceOption', { name: 'hideUserStats', value: hideUserStats }) + store.dispatch('setInstanceOption', { name: 'logo', value: logo }) + store.dispatch('setInstanceOption', { name: 'logoMask', value: logoMask }) + store.dispatch('setInstanceOption', { name: 'logoMargin', value: logoMargin }) + store.dispatch('setInstanceOption', { name: 'redirectRootNoLogin', value: redirectRootNoLogin }) + store.dispatch('setInstanceOption', { name: 'redirectRootLogin', value: redirectRootLogin }) + store.dispatch('setInstanceOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel }) + store.dispatch('setInstanceOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled }) + store.dispatch('setInstanceOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled }) + store.dispatch('setInstanceOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject }) + store.dispatch('setInstanceOption', { name: 'loginMethod', value: loginMethod }) + if (chatDisabled) { + store.dispatch('disableChat') + } + + const routes = [ + { name: 'root', + path: '/', + redirect: to => { + return (store.state.users.currentUser + ? store.state.instance.redirectRootLogin + : store.state.instance.redirectRootNoLogin) || '/main/all' + }}, + { path: '/main/all', component: PublicAndExternalTimeline }, + { path: '/main/public', component: PublicTimeline }, + { path: '/main/friends', component: FriendsTimeline }, + { path: '/tag/:tag', component: TagTimeline }, + { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { name: 'user-profile', path: '/users/:id', component: UserProfile }, + { name: 'mentions', path: '/:username/mentions', component: Mentions }, + { name: 'settings', path: '/settings', component: Settings }, + { name: 'registration', path: '/registration', component: Registration }, + { name: 'registration', path: '/registration/:token', component: Registration }, + { name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, + { name: 'user-settings', path: '/user-settings', component: UserSettings }, + { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) } + ] + + const router = new VueRouter({ + mode: 'history', + routes, + scrollBehavior: (to, from, savedPosition) => { + if (to.matched.some(m => m.meta.dontScroll)) { + return false + } + return savedPosition || { x: 0, y: 0 } + } + }) + + /* eslint-disable no-new */ + new Vue({ + router, + store, + i18n, + el: '#app', + render: h => h(App) + }) + }) + }) + + window.fetch('/static/terms-of-service.html') + .then((res) => res.text()) + .then((html) => { + store.dispatch('setInstanceOption', { name: 'tos', value: html }) + }) + + window.fetch('/api/pleroma/emoji.json') + .then( + (res) => res.json() + .then( + (values) => { + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: values[key] } + }) + store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) + }, + (failure) => { + store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) + } + ), + (error) => console.log(error) + ) + + window.fetch('/static/emoji.json') + .then((res) => res.json()) + .then((values) => { + const emoji = Object.keys(values).map((key) => { + return { shortcode: key, image_url: false, 'utf': values[key] } + }) + store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) + }) + + window.fetch('/instance/panel.html') + .then((res) => res.text()) + .then((html) => { + store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) + }) + + window.fetch('/nodeinfo/2.0.json') + .then((res) => res.json()) + .then((data) => { + const metadata = data.metadata + + const features = metadata.features + store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) + store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) + store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) + + const suggestions = metadata.suggestions + store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) + store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) + }) +} + +export default afterStoreSetup diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 4405fb92..49868aed 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,22 +1,40 @@ +import oauthApi from '../../services/new_api/oauth.js' const LoginForm = { data: () => ({ user: {}, authError: false }), computed: { + loginMethod () { return this.$store.state.instance.loginMethod }, loggingIn () { return this.$store.state.users.loggingIn }, registrationOpen () { return this.$store.state.instance.registrationOpen } }, methods: { + oAuthLogin () { + oauthApi.login({ + oauth: this.$store.state.oauth, + instance: this.$store.state.instance.server, + commit: this.$store.commit + }) + }, submit () { - this.$store.dispatch('loginUser', this.user).then( - () => {}, - (error) => { - this.authError = error - this.user.username = '' - this.user.password = '' - } - ) + const data = { + oauth: this.$store.state.oauth, + instance: this.$store.state.instance.server + } + oauthApi.getOrCreateApp(data).then((app) => { + oauthApi.getTokenWithCredentials( + { + app, + instance: data.instance, + username: this.user.username, + password: this.user.password}) + .then((result) => { + this.$store.commit('setToken', result.access_token) + this.$store.dispatch('loginUser', result.access_token) + this.$router.push('/main/friends') + }) + }) } } } diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index b7fed48a..12971882 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -5,7 +5,7 @@ {{$t('login.login')}} </div> <div class="panel-body"> - <form v-on:submit.prevent='submit(user)' class='login-form'> + <form v-if="loginMethod == 'password'" v-on:submit.prevent='submit(user)' class='login-form'> <div class='form-group'> <label for='username'>{{$t('login.username')}}</label> <input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' v-bind:placeholder="$t('login.placeholder')"> @@ -20,8 +20,17 @@ <button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button> </div> </div> - <div v-if="authError" class='form-group'> - <div class='alert error'>{{authError}}</div> + </form> + + <form v-if="loginMethod == 'token'" v-on:submit.prevent='oAuthLogin' class="login-form"> + <div class="form-group"> + <p>{{$t('login.description')}}</p> + </div> + <div class='form-group'> + <div class='login-bottom'> + <div><router-link :to="{name: 'registration'}" v-if='registrationOpen' class='register'>{{$t('login.register')}}</router-link></div> + <button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button> + </div> </div> </form> </div> diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js new file mode 100644 index 00000000..7a5132ad --- /dev/null +++ b/src/components/oauth_callback/oauth_callback.js @@ -0,0 +1,20 @@ +import oauth from '../../services/new_api/oauth.js' + +const oac = { + props: ['code'], + mounted () { + if (this.code) { + oauth.getToken({ + app: this.$store.state.oauth, + instance: this.$store.state.instance.server, + code: this.code + }).then((result) => { + this.$store.commit('setToken', result.access_token) + this.$store.dispatch('loginUser', result.access_token) + this.$router.push('/main/friends') + }) + } + } +} + +export default oac diff --git a/src/components/oauth_callback/oauth_callback.vue b/src/components/oauth_callback/oauth_callback.vue new file mode 100644 index 00000000..9c806916 --- /dev/null +++ b/src/components/oauth_callback/oauth_callback.vue @@ -0,0 +1,5 @@ +<template> + <h1>...</h1> +</template> + +<script src="./oauth_callback.js"></script> diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index 8f59878d..f7f8a720 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -1,3 +1,5 @@ +import oauthApi from '../../services/new_api/oauth.js' + const registration = { data: () => ({ user: {}, @@ -25,9 +27,23 @@ const registration = { this.$store.state.api.backendInteractor.register(this.user).then( (response) => { if (response.ok) { - this.$store.dispatch('loginUser', this.user) - this.$router.push('/main/all') - this.registering = false + const data = { + oauth: this.$store.state.oauth, + instance: this.$store.state.instance.server + } + oauthApi.getOrCreateApp(data).then((app) => { + oauthApi.getTokenWithCredentials( + { + app, + instance: data.instance, + username: this.user.username, + password: this.user.password}) + .then((result) => { + this.$store.commit('setToken', result.access_token) + this.$store.dispatch('loginUser', result.access_token) + this.$router.push('/main/friends') + }) + }) } else { this.registering = false response.json().then((data) => { diff --git a/src/i18n/de.json b/src/i18n/de.json index 4e742bad..49179423 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -12,6 +12,7 @@ }, "login": { "login": "Anmelden", + "description": "Mit OAuth anmelden", "logout": "Abmelden", "password": "Passwort", "placeholder": "z.B. lain", diff --git a/src/i18n/en.json b/src/i18n/en.json index 8963e479..036231bf 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -21,6 +21,7 @@ }, "login": { "login": "Log in", + "description": "Log in with OAuth", "logout": "Log out", "password": "Password", "placeholder": "e.g. lain", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 006107e2..32fc93c6 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -17,7 +17,9 @@ const saveImmedeatelyActions = [ 'clearCurrentUser', 'setCurrentUser', 'setHighlight', - 'setOption' + 'setOption', + 'setClientData', + 'setToken' ] const defaultStorage = (() => { @@ -43,8 +45,8 @@ export default function createPersistedState ({ storage = defaultStorage, subscriber = store => handler => store.subscribe(handler) } = {}) { - return store => { - getState(key, storage).then((savedState) => { + return getState(key, storage).then((savedState) => { + return store => { try { if (typeof savedState === 'object') { // build user cache @@ -67,36 +69,35 @@ export default function createPersistedState ({ value: store.state.config.customTheme }) } - if (store.state.users.lastLoginName) { - store.dispatch('loginUser', {username: store.state.users.lastLoginName, password: 'xxx'}) + if (store.state.oauth.token) { + store.dispatch('loginUser', store.state.oauth.token) } loaded = true } catch (e) { console.log("Couldn't load state") loaded = true } - }) - - subscriber(store)((mutation, state) => { - try { - if (saveImmedeatelyActions.includes(mutation.type)) { - setState(key, reducer(state, paths), storage) - .then(success => { - if (typeof success !== 'undefined') { + subscriber(store)((mutation, state) => { + try { + if (saveImmedeatelyActions.includes(mutation.type)) { + setState(key, reducer(state, paths), storage) + .then(success => { + if (typeof success !== 'undefined') { + if (mutation.type === 'setOption') { + store.dispatch('settingsSaved', { success }) + } + } + }, error => { if (mutation.type === 'setOption') { - store.dispatch('settingsSaved', { success }) + store.dispatch('settingsSaved', { error }) } - } - }, error => { - if (mutation.type === 'setOption') { - store.dispatch('settingsSaved', { error }) - } - }) + }) + } + } catch (e) { + console.log("Couldn't persist state:") + console.log(e) } - } catch (e) { - console.log("Couldn't persist state:") - console.log(e) - } - }) - } + }) + } + }) } diff --git a/src/main.js b/src/main.js index b71ae4cb..9f094ada 100644 --- a/src/main.js +++ b/src/main.js @@ -1,18 +1,6 @@ import Vue from 'vue' import VueRouter from 'vue-router' import Vuex from 'vuex' -import App from './App.vue' -import PublicTimeline from './components/public_timeline/public_timeline.vue' -import PublicAndExternalTimeline from './components/public_and_external_timeline/public_and_external_timeline.vue' -import FriendsTimeline from './components/friends_timeline/friends_timeline.vue' -import TagTimeline from './components/tag_timeline/tag_timeline.vue' -import ConversationPage from './components/conversation-page/conversation-page.vue' -import Mentions from './components/mentions/mentions.vue' -import UserProfile from './components/user_profile/user_profile.vue' -import Settings from './components/settings/settings.vue' -import Registration from './components/registration/registration.vue' -import UserSettings from './components/user_settings/user_settings.vue' -import FollowRequests from './components/follow_requests/follow_requests.vue' import interfaceModule from './modules/interface.js' import instanceModule from './modules/instance.js' @@ -21,6 +9,7 @@ import usersModule from './modules/users.js' import apiModule from './modules/api.js' import configModule from './modules/config.js' import chatModule from './modules/chat.js' +import oauthModule from './modules/oauth.js' import VueTimeago from 'vue-timeago' import VueI18n from 'vue-i18n' @@ -31,6 +20,8 @@ import messages from './i18n/messages.js' import VueChatScroll from 'vue-chat-scroll' +import afterStoreSetup from './boot/after_store.js' + const currentLocale = (window.navigator.language || 'en').split('-')[0] Vue.use(Vuex) @@ -45,29 +36,6 @@ Vue.use(VueTimeago, { Vue.use(VueI18n) Vue.use(VueChatScroll) -const persistedStateOptions = { - paths: [ - 'config', - 'users.lastLoginName', - 'statuses.notifications.maxSavedId' - ] -} - -const store = new Vuex.Store({ - modules: { - interface: interfaceModule, - instance: instanceModule, - statuses: statusesModule, - users: usersModule, - api: apiModule, - config: configModule, - chat: chatModule - }, - plugins: [createPersistedState(persistedStateOptions)], - strict: false // Socket modifies itself, let's ignore this for now. - // strict: process.env.NODE_ENV !== 'production' -}) - const i18n = new VueI18n({ // By default, use the browser locale, we will update it if neccessary locale: currentLocale, @@ -75,155 +43,30 @@ const i18n = new VueI18n({ messages }) -window.fetch('/api/statusnet/config.json') - .then((res) => res.json()) - .then((data) => { - const {name, closed: registrationClosed, textlimit, server} = data.site - - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) - - var apiConfig = data.site.pleromafe - - window.fetch('/static/config.json') - .then((res) => res.json()) - .catch((err) => { - console.warn('Failed to load static/config.json, continuing without it.') - console.warn(err) - return {} - }) - .then((staticConfig) => { - // This takes static config and overrides properties that are present in apiConfig - var config = Object.assign({}, staticConfig, apiConfig) - - var theme = (config.theme) - var background = (config.background) - var hidePostStats = (config.hidePostStats) - var hideUserStats = (config.hideUserStats) - var logo = (config.logo) - var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask) - var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin) - var redirectRootNoLogin = (config.redirectRootNoLogin) - var redirectRootLogin = (config.redirectRootLogin) - var chatDisabled = (config.chatDisabled) - var showInstanceSpecificPanel = (config.showInstanceSpecificPanel) - var scopeOptionsEnabled = (config.scopeOptionsEnabled) - var formattingOptionsEnabled = (config.formattingOptionsEnabled) - var collapseMessageWithSubject = (config.collapseMessageWithSubject) - - store.dispatch('setInstanceOption', { name: 'theme', value: theme }) - store.dispatch('setInstanceOption', { name: 'background', value: background }) - store.dispatch('setInstanceOption', { name: 'hidePostStats', value: hidePostStats }) - store.dispatch('setInstanceOption', { name: 'hideUserStats', value: hideUserStats }) - store.dispatch('setInstanceOption', { name: 'logo', value: logo }) - store.dispatch('setInstanceOption', { name: 'logoMask', value: logoMask }) - store.dispatch('setInstanceOption', { name: 'logoMargin', value: logoMargin }) - store.dispatch('setInstanceOption', { name: 'redirectRootNoLogin', value: redirectRootNoLogin }) - store.dispatch('setInstanceOption', { name: 'redirectRootLogin', value: redirectRootLogin }) - store.dispatch('setInstanceOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel }) - store.dispatch('setInstanceOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled }) - store.dispatch('setInstanceOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled }) - store.dispatch('setInstanceOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject }) - if (chatDisabled) { - store.dispatch('disableChat') - } - - const routes = [ - { name: 'root', - path: '/', - redirect: to => { - return (store.state.users.currentUser - ? store.state.instance.redirectRootLogin - : store.state.instance.redirectRootNoLogin) || '/main/all' - }}, - { path: '/main/all', component: PublicAndExternalTimeline }, - { path: '/main/public', component: PublicTimeline }, - { path: '/main/friends', component: FriendsTimeline }, - { path: '/tag/:tag', component: TagTimeline }, - { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, - { name: 'user-profile', path: '/users/:id', component: UserProfile }, - { name: 'mentions', path: '/:username/mentions', component: Mentions }, - { name: 'settings', path: '/settings', component: Settings }, - { name: 'registration', path: '/registration', component: Registration }, - { name: 'registration', path: '/registration/:token', component: Registration }, - { name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, - { name: 'user-settings', path: '/user-settings', component: UserSettings } - ] - - const router = new VueRouter({ - mode: 'history', - routes, - scrollBehavior: (to, from, savedPosition) => { - if (to.matched.some(m => m.meta.dontScroll)) { - return false - } - return savedPosition || { x: 0, y: 0 } - } - }) - - /* eslint-disable no-new */ - new Vue({ - router, - store, - i18n, - el: '#app', - render: h => h(App) - }) - }) - }) - -window.fetch('/static/terms-of-service.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'tos', value: html }) - }) - -window.fetch('/api/pleroma/emoji.json') - .then( - (res) => res.json() - .then( - (values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: values[key] } - }) - store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji }) - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true }) - }, - (failure) => { - store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false }) - } - ), - (error) => console.log(error) - ) - -window.fetch('/static/emoji.json') - .then((res) => res.json()) - .then((values) => { - const emoji = Object.keys(values).map((key) => { - return { shortcode: key, image_url: false, 'utf': values[key] } - }) - store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) - }) - -window.fetch('/instance/panel.html') - .then((res) => res.text()) - .then((html) => { - store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) +const persistedStateOptions = { + paths: [ + 'config', + 'users.lastLoginName', + 'statuses.notifications.maxSavedId', + 'oauth' + ] +} +createPersistedState(persistedStateOptions).then((persistedState) => { + const store = new Vuex.Store({ + modules: { + interface: interfaceModule, + instance: instanceModule, + statuses: statusesModule, + users: usersModule, + api: apiModule, + config: configModule, + chat: chatModule, + oauth: oauthModule + }, + plugins: [persistedState], + strict: false // Socket modifies itself, let's ignore this for now. + // strict: process.env.NODE_ENV !== 'production' }) -window.fetch('/nodeinfo/2.0.json') - .then((res) => res.json()) - .then((data) => { - const metadata = data.metadata - - const features = metadata.features - store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) - store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) - store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) - - const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled }) - store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web }) - }) + afterStoreSetup({store, i18n}) +}) diff --git a/src/modules/instance.js b/src/modules/instance.js index d61ca842..8b2c03d2 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -21,6 +21,7 @@ const defaultState = { hidePostStats: false, hideUserStats: false, disableChat: false, + loginMethod: 'password', // Nasty stuff pleromaBackend: true, diff --git a/src/modules/oauth.js b/src/modules/oauth.js new file mode 100644 index 00000000..144ff830 --- /dev/null +++ b/src/modules/oauth.js @@ -0,0 +1,18 @@ +const oauth = { + state: { + client_id: false, + client_secret: false, + token: false + }, + mutations: { + setClientData (state, data) { + state.client_id = data.client_id + state.client_secret = data.client_secret + }, + setToken (state, token) { + state.token = token + } + } +} + +export default oauth diff --git a/src/modules/users.js b/src/modules/users.js index e90d6bb9..8630ee0d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -82,24 +82,26 @@ const users = { }, logout (store) { store.commit('clearCurrentUser') + store.commit('setToken', false) store.dispatch('stopFetching', 'friends') store.commit('setBackendInteractor', backendInteractorService()) }, - loginUser (store, userCredentials) { + loginUser (store, accessToken) { return new Promise((resolve, reject) => { const commit = store.commit commit('beginLogin') - store.rootState.api.backendInteractor.verifyCredentials(userCredentials) + store.rootState.api.backendInteractor.verifyCredentials(accessToken) .then((response) => { if (response.ok) { response.json() .then((user) => { - user.credentials = userCredentials + // user.credentials = userCredentials + user.credentials = accessToken commit('setCurrentUser', user) commit('addNewUsers', [user]) // Set our new backend interactor - commit('setBackendInteractor', backendInteractorService(userCredentials)) + commit('setBackendInteractor', backendInteractorService(accessToken)) if (user.token) { store.dispatch('initializeSocket', user.token) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ab746918..499e9b16 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -52,16 +52,6 @@ let fetch = (url, options) => { return oldfetch(fullUrl, options) } -// from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding -let utoa = (str) => { - // first we use encodeURIComponent to get percent-encoded UTF-8, - // then we convert the percent encodings into raw bytes which - // can be fed into btoa. - return btoa(encodeURIComponent(str) - .replace(/%([0-9A-F]{2})/g, - (match, p1) => { return String.fromCharCode('0x' + p1) })) -} - // Params // cropH // cropW @@ -175,9 +165,9 @@ const register = (params) => { }) } -const authHeaders = (user) => { - if (user && user.username && user.password) { - return { 'Authorization': `Basic ${utoa(`${user.username}:${user.password}`)}` } +const authHeaders = (accessToken) => { + if (accessToken) { + return { 'Authorization': `Bearer ${accessToken}` } } else { return { } } diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js new file mode 100644 index 00000000..9e656507 --- /dev/null +++ b/src/services/new_api/oauth.js @@ -0,0 +1,82 @@ +import {reduce} from 'lodash' + +const getOrCreateApp = ({oauth, instance}) => { + const url = `${instance}/api/v1/apps` + const form = new window.FormData() + + form.append('client_name', `PleromaFE_${Math.random()}`) + form.append('redirect_uris', `${window.location.origin}/oauth-callback`) + form.append('scopes', 'read write follow') + + return window.fetch(url, { + method: 'POST', + body: form + }).then((data) => data.json()) +} +const login = (args) => { + getOrCreateApp(args).then((app) => { + args.commit('setClientData', app) + + const data = { + response_type: 'code', + client_id: app.client_id, + redirect_uri: app.redirect_uri, + scope: 'read write follow' + } + + const dataString = reduce(data, (acc, v, k) => { + const encoded = `${k}=${encodeURIComponent(v)}` + if (!acc) { + return encoded + } else { + return `${acc}&${encoded}` + } + }, false) + + // Do the redirect... + const url = `${args.instance}/oauth/authorize?${dataString}` + + window.location.href = url + }) +} + +const getTokenWithCredentials = ({app, instance, username, password}) => { + const url = `${instance}/oauth/token` + const form = new window.FormData() + + form.append('client_id', app.client_id) + form.append('client_secret', app.client_secret) + form.append('grant_type', 'password') + form.append('username', username) + form.append('password', password) + + return window.fetch(url, { + method: 'POST', + body: form + }).then((data) => data.json()) +} + +const getToken = ({app, instance, code}) => { + const url = `${instance}/oauth/token` + const form = new window.FormData() + + form.append('client_id', app.client_id) + form.append('client_secret', app.client_secret) + form.append('grant_type', 'authorization_code') + form.append('code', code) + form.append('redirect_uri', `${window.location.origin}/oauth-callback`) + + return window.fetch(url, { + method: 'POST', + body: form + }).then((data) => data.json()) +} + +const oauth = { + login, + getToken, + getTokenWithCredentials, + getOrCreateApp +} + +export default oauth |
