[refactor] Remove authorize store
This commit is contained in:
parent
b46c9c2e91
commit
df5ecdf9fd
|
@ -11,7 +11,7 @@ export const insertAccount = (
|
|||
clientSecret: string,
|
||||
accessToken: string,
|
||||
refreshToken: string | null,
|
||||
server: LocalServer
|
||||
serverId: number
|
||||
): Promise<LocalAccount> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
|
@ -34,13 +34,12 @@ export const insertAccount = (
|
|||
}
|
||||
const id = this.lastID
|
||||
|
||||
db.run('UPDATE servers SET account_id = ? WHERE id = ?', [id, server.id], err => {
|
||||
db.run('UPDATE servers SET account_id = ? WHERE id = ?', [id, serverId], err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
db.run('COMMIT')
|
||||
|
||||
resolve({
|
||||
id,
|
||||
username,
|
||||
|
@ -164,6 +163,9 @@ FROM accounts INNER JOIN servers ON servers.account_id = accounts.id WHERE accou
|
|||
|
||||
export const removeAccount = (db: sqlite3.Database, id: number): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('PRAGMA foreign_keys = ON')
|
||||
|
||||
db.run('DELETE FROM accounts WHERE id = ?', id, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
|
@ -171,10 +173,14 @@ export const removeAccount = (db: sqlite3.Database, id: number): Promise<null> =
|
|||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const removeAllAccounts = (db: sqlite3.Database): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('PRAGMA foreign_keys = ON')
|
||||
|
||||
db.run('DELETE FROM accounts', err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
|
@ -182,6 +188,7 @@ export const removeAllAccounts = (db: sqlite3.Database): Promise<null> => {
|
|||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const forwardAccount = (db: sqlite3.Database, id: number): Promise<null> => {
|
||||
|
|
|
@ -53,6 +53,9 @@ export const insertTag = (db: sqlite3.Database, accountId: number, tag: string):
|
|||
|
||||
export const removeTag = (db: sqlite3.Database, tag: LocalTag): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('PRAGMA foreign_keys = ON')
|
||||
|
||||
db.run('DELETE FROM hashtags WHERE id = ?', tag.id, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
|
@ -60,4 +63,5 @@ export const removeTag = (db: sqlite3.Database, tag: LocalTag): Promise<null> =>
|
|||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -463,26 +463,28 @@ ipcMain.handle('add-app', async (_: IpcMainInvokeEvent, url: string) => {
|
|||
})
|
||||
|
||||
type AuthorizeRequest = {
|
||||
server: LocalServer
|
||||
appData: OAuth.AppData
|
||||
serverID: number
|
||||
baseURL: string
|
||||
clientID: string
|
||||
clientSecret: string
|
||||
code: string
|
||||
}
|
||||
|
||||
ipcMain.handle('authorize', async (_: IpcMainInvokeEvent, req: AuthorizeRequest) => {
|
||||
const proxy = await proxyConfiguration.forMastodon()
|
||||
const sns = await detector(req.server.baseURL, proxy)
|
||||
const client = generator(sns, req.server.baseURL, null, 'Whalebird', proxy)
|
||||
const tokenData = await client.fetchAccessToken(req.appData.client_id, req.appData.client_secret, req.code, 'urn:ietf:wg:oauth:2.0:oob')
|
||||
const sns = await detector(req.baseURL, proxy)
|
||||
const client = generator(sns, req.baseURL, null, 'Whalebird', proxy)
|
||||
const tokenData = await client.fetchAccessToken(req.clientID, req.clientSecret, req.code, 'urn:ietf:wg:oauth:2.0:oob')
|
||||
let accessToken = tokenData.access_token
|
||||
if (sns === 'misskey') {
|
||||
// In misskey, access token is sha256(userToken + clientSecret)
|
||||
accessToken = crypto
|
||||
.createHash('sha256')
|
||||
.update(tokenData.access_token + req.appData.client_secret, 'utf8')
|
||||
.update(tokenData.access_token + req.clientSecret, 'utf8')
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
const authorizedClient = generator(sns, req.server.baseURL, accessToken, 'Whalebird', proxy)
|
||||
const authorizedClient = generator(sns, req.baseURL, accessToken, 'Whalebird', proxy)
|
||||
const credentials = await authorizedClient.verifyAccountCredentials()
|
||||
|
||||
const account = await insertAccount(
|
||||
|
@ -490,11 +492,11 @@ ipcMain.handle('authorize', async (_: IpcMainInvokeEvent, req: AuthorizeRequest)
|
|||
credentials.data.username,
|
||||
credentials.data.id,
|
||||
credentials.data.avatar,
|
||||
req.appData.client_id,
|
||||
req.appData.client_secret,
|
||||
req.clientID,
|
||||
req.clientSecret,
|
||||
accessToken,
|
||||
tokenData.refresh_token,
|
||||
req.server
|
||||
req.serverID
|
||||
)
|
||||
return account
|
||||
})
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<FailoverImg :src="`${server.baseURL}/favicon.ico`" :failoverSrc="`${server.baseURL}/favicon.png`" class="instance-icon" />
|
||||
<span>{{ server.domain }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/login" :title="$t('global_header.add_new_account')" role="menuitem" class="add-new-account">
|
||||
<el-menu-item index="/login/form" :title="$t('global_header.add_new_account')" role="menuitem" class="add-new-account">
|
||||
<font-awesome-icon icon="plus" />
|
||||
<span>New</span>
|
||||
</el-menu-item>
|
||||
|
@ -71,7 +71,7 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
return router.push({ path: '/login' })
|
||||
return router.push({ path: '/login/form' })
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -10,38 +10,27 @@
|
|||
</el-row>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<login-form v-if="appData === null" />
|
||||
<authorize v-else />
|
||||
<router-view />
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '@/store'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import LoginForm from './Login/LoginForm.vue'
|
||||
import Authorize from './Login/Authorize.vue'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'login',
|
||||
components: { LoginForm, Authorize },
|
||||
setup() {
|
||||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const { escape } = useMagicKeys()
|
||||
|
||||
const appData = computed(() => store.state.Login.appData)
|
||||
|
||||
whenever(escape, () => {
|
||||
close()
|
||||
})
|
||||
|
||||
const close = () => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.PAGE_BACK}`)
|
||||
return router.push({
|
||||
path: '/',
|
||||
query: { redirect: 'home' }
|
||||
|
@ -49,8 +38,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
close,
|
||||
appData
|
||||
close
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
class="authorize-form"
|
||||
@submit.prevent="authorizeSubmit"
|
||||
>
|
||||
<p v-if="sns === 'misskey'">{{ $t('authorize.misskey_label') }}</p>
|
||||
<p v-if="$route.query.sns === 'misskey'">{{ $t('authorize.misskey_label') }}</p>
|
||||
<el-form-item :label="$t('authorize.code_label')" v-else>
|
||||
<el-input v-model="authorizeForm.code"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -37,27 +37,25 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { defineComponent, ref, reactive } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Authorize',
|
||||
setup() {
|
||||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const win = (window as any) as MyWindow
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { escape } = useMagicKeys()
|
||||
|
||||
const sns = computed(() => store.state.Login.sns)
|
||||
|
||||
const authorizeForm = reactive({
|
||||
code: null
|
||||
code: ''
|
||||
})
|
||||
const submitting = ref<boolean>(false)
|
||||
|
||||
|
@ -65,23 +63,30 @@ export default defineComponent({
|
|||
close()
|
||||
})
|
||||
|
||||
const authorizeSubmit = () => {
|
||||
const authorizeSubmit = async () => {
|
||||
submitting.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.AUTHORIZE}`, authorizeForm.code)
|
||||
.finally(() => {
|
||||
submitting.value = false
|
||||
let code = authorizeForm.code
|
||||
if (route.query.sns === 'misskey' && route.query.session_token) {
|
||||
code = route.query.session_token.toString()
|
||||
}
|
||||
try {
|
||||
const localAccount: LocalAccount = await win.ipcRenderer.invoke('authorize', {
|
||||
serverID: route.query.server_id,
|
||||
baseURL: route.query.base_url,
|
||||
clientID: route.query.client_id,
|
||||
clientSecret: route.query.client_secret,
|
||||
code: code
|
||||
})
|
||||
.then(id => {
|
||||
router.push({ path: `/${id}/home` })
|
||||
})
|
||||
.catch(err => {
|
||||
router.push({ path: `/${localAccount.id}/home` })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
|
@ -92,8 +97,7 @@ export default defineComponent({
|
|||
authorizeForm,
|
||||
submitting,
|
||||
authorizeSubmit,
|
||||
close,
|
||||
sns
|
||||
close
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -40,25 +40,28 @@
|
|||
import { defineComponent, computed, reactive, ref } from 'vue'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElLoading, ElMessage, FormInstance, FormRules } from 'element-plus'
|
||||
import { useStore } from '@/store'
|
||||
import { domainFormat } from '@/utils/validator'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
import { detector, OAuth } from 'megalodon'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'login-form',
|
||||
setup() {
|
||||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const i18n = useI18next()
|
||||
const router = useRouter()
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const form = reactive({
|
||||
domainName: ''
|
||||
})
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
const domain = ref<string>('')
|
||||
const searching = ref<boolean>(false)
|
||||
const allowLogin = computed(() => domain.value && form.domainName == domain.value)
|
||||
const sns = ref<'mastodon' | 'pleroma' | 'misskey'>('mastodon')
|
||||
|
||||
const selectedInstance = computed(() => store.state.Login.domain)
|
||||
const searching = computed(() => store.state.Login.searching)
|
||||
const allowLogin = computed(() => selectedInstance.value && form.domainName === selectedInstance.value)
|
||||
const rules = reactive<FormRules>({
|
||||
domainName: [
|
||||
{
|
||||
|
@ -81,8 +84,20 @@ export default defineComponent({
|
|||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.ADD_SERVER}`)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.ADD_APP}`)
|
||||
const server: LocalServer = await win.ipcRenderer.invoke('add-server', domain.value)
|
||||
const appData: OAuth.AppData = await win.ipcRenderer.invoke('add-app', `https://${domain.value}`)
|
||||
router.push({
|
||||
path: '/login/authorize',
|
||||
query: {
|
||||
server_id: server.id,
|
||||
base_url: server.baseURL,
|
||||
client_id: appData.client_id,
|
||||
client_secret: appData.client_secret,
|
||||
session_token: appData.session_token,
|
||||
sns: sns.value,
|
||||
url: appData.url
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_url_error'),
|
||||
|
@ -96,27 +111,31 @@ export default defineComponent({
|
|||
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(valid => {
|
||||
await formEl.validate(async valid => {
|
||||
if (valid) {
|
||||
return store
|
||||
.dispatch(`${space}/${ACTION_TYPES.CONFIRM_INSTANCE}`, form.domainName)
|
||||
.then(() => {
|
||||
searching.value = true
|
||||
try {
|
||||
const cleanDomain = form.domainName.trim()
|
||||
sns.value = await detector(`https://${cleanDomain}`)
|
||||
domain.value = cleanDomain
|
||||
ElMessage({
|
||||
message: i18n.t('message.domain_confirmed', {
|
||||
domain: form.domainName
|
||||
domain: cleanDomain
|
||||
}),
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
ElMessage({
|
||||
message: i18n.t('message.domain_doesnt_exist', {
|
||||
domain: form.domainName
|
||||
}),
|
||||
type: 'error'
|
||||
})
|
||||
console.error(err)
|
||||
})
|
||||
} finally {
|
||||
searching.value = false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
ElMessage({
|
||||
message: i18n.t('validation.login.domain_format'),
|
||||
|
|
|
@ -153,7 +153,7 @@ export default defineComponent({
|
|||
|
||||
const removeAllAssociations = () => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.REMOVE_ALL_ACCOUNTS}`).then(() => {
|
||||
router.push('/login')
|
||||
router.push('/login/form')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import Login from '@/components/Login.vue'
|
||||
import LoginForm from '@/components/Login/LoginForm.vue'
|
||||
import Authorize from '@/components/Login/Authorize.vue'
|
||||
import Preferences from '@/components/Preferences.vue'
|
||||
import PreferencesGeneral from '@/components/Preferences/General.vue'
|
||||
import PreferencesAppearance from '@/components/Preferences/Appearance.vue'
|
||||
|
@ -34,9 +36,21 @@ import TimelineSpaceContentsBookmarks from '@/components/TimelineSpace/Contents/
|
|||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
path: '/login/',
|
||||
name: 'login',
|
||||
component: Login
|
||||
component: Login,
|
||||
children: [
|
||||
{
|
||||
path: 'form',
|
||||
name: 'login-form',
|
||||
component: LoginForm
|
||||
},
|
||||
{
|
||||
path: 'authorize',
|
||||
name: 'authorize',
|
||||
component: Authorize
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/preferences/',
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { detector } from 'megalodon'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
import { OAuth } from 'megalodon'
|
||||
import { LocalAccount } from '~src/types/localAccount'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
export type LoginState = {
|
||||
domain: string | null
|
||||
searching: boolean
|
||||
server: LocalServer | null
|
||||
appData: OAuth.AppData | null
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey'
|
||||
}
|
||||
|
||||
const state = (): LoginState => ({
|
||||
domain: null,
|
||||
searching: false,
|
||||
server: null,
|
||||
appData: null,
|
||||
sns: 'mastodon'
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_DOMAIN: 'changeDomain',
|
||||
CHANGE_SEARCHING: 'changeSearching',
|
||||
CHANGE_SERVER: 'changeServer',
|
||||
CHANGE_APP_DATA: 'changeAppData',
|
||||
CHANGE_SNS: 'changeSNS'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<LoginState> = {
|
||||
[MUTATION_TYPES.CHANGE_DOMAIN]: (state: LoginState, instance: string | null) => {
|
||||
state.domain = instance
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SEARCHING]: (state: LoginState, searching: boolean) => {
|
||||
state.searching = searching
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SERVER]: (state: LoginState, server: LocalServer | null) => {
|
||||
state.server = server
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_APP_DATA]: (state: LoginState, appData: OAuth.AppData | null) => {
|
||||
state.appData = appData
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SNS]: (state: LoginState, sns: 'mastodon' | 'pleroma' | 'misskey') => {
|
||||
state.sns = sns
|
||||
}
|
||||
}
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
ADD_SERVER: 'addServer',
|
||||
ADD_APP: 'addApp',
|
||||
AUTHORIZE: 'authorize',
|
||||
PAGE_BACK: 'pageBack',
|
||||
CONFIRM_INSTANCE: 'confirmInstance'
|
||||
}
|
||||
|
||||
const actions: ActionTree<LoginState, RootState> = {
|
||||
[ACTION_TYPES.ADD_SERVER]: async ({ state, commit }): Promise<LocalServer> => {
|
||||
const server = await win.ipcRenderer.invoke('add-server', state.domain)
|
||||
commit(MUTATION_TYPES.CHANGE_SERVER, server)
|
||||
return server
|
||||
},
|
||||
[ACTION_TYPES.ADD_APP]: async ({ state, commit }) => {
|
||||
const appData = await win.ipcRenderer.invoke('add-app', `https://${state.domain}`)
|
||||
commit(MUTATION_TYPES.CHANGE_APP_DATA, appData)
|
||||
},
|
||||
[ACTION_TYPES.AUTHORIZE]: async ({ state }, code: string): Promise<number> => {
|
||||
const localAccount: LocalAccount = await win.ipcRenderer.invoke('authorize', {
|
||||
server: toRaw(state.server),
|
||||
appData: toRaw(state.appData),
|
||||
code
|
||||
})
|
||||
return localAccount.id
|
||||
},
|
||||
[ACTION_TYPES.PAGE_BACK]: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.CHANGE_DOMAIN, null)
|
||||
commit(MUTATION_TYPES.CHANGE_SERVER, null)
|
||||
commit(MUTATION_TYPES.CHANGE_APP_DATA, null)
|
||||
},
|
||||
[ACTION_TYPES.CONFIRM_INSTANCE]: async ({ commit }, domain: string): Promise<boolean> => {
|
||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, true)
|
||||
const cleanDomain = domain.trim()
|
||||
try {
|
||||
const sns = await detector(`https://${cleanDomain}`)
|
||||
commit(MUTATION_TYPES.CHANGE_DOMAIN, cleanDomain)
|
||||
commit(MUTATION_TYPES.CHANGE_SNS, sns)
|
||||
} finally {
|
||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const Login: Module<LoginState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Login
|
|
@ -4,7 +4,6 @@ import { InjectionKey } from 'vue'
|
|||
|
||||
import App, { AppState } from './App'
|
||||
import GlobalHeader, { GlobalHeaderState } from './GlobalHeader'
|
||||
import Login, { LoginState } from './Login'
|
||||
import TimelineSpace, { TimelineSpaceModuleState } from './TimelineSpace'
|
||||
import Preferences, { PreferencesModuleState } from './Preferences'
|
||||
import Settings, { SettingsModuleState } from './Settings'
|
||||
|
@ -15,7 +14,6 @@ const win = (window as any) as MyWindow
|
|||
export interface RootState {
|
||||
App: AppState
|
||||
GlobalHeader: GlobalHeaderState
|
||||
Login: LoginState
|
||||
TimelineSpace: TimelineSpaceModuleState
|
||||
Preferences: PreferencesModuleState
|
||||
Settings: SettingsModuleState
|
||||
|
@ -34,7 +32,6 @@ export default createStore({
|
|||
modules: {
|
||||
App,
|
||||
GlobalHeader,
|
||||
Login,
|
||||
TimelineSpace,
|
||||
Preferences,
|
||||
Settings
|
||||
|
|
Loading…
Reference in New Issue