Merge branch 'api-refactor' into shigusegubu-themes3
This commit is contained in:
commit
cfefb70b1f
14 changed files with 337 additions and 433 deletions
45
src/api/mfa.js
Normal file
45
src/api/mfa.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { promisedRequest } from './helpers.js'
|
||||
|
||||
export const verifyOTPCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'totp')
|
||||
|
||||
return promisedRequest({
|
||||
url: '/oauth/mfa/challenge',
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const verifyRecoveryCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'recovery')
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/mfa/challenge`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
138
src/api/oauth.js
Normal file
138
src/api/oauth.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { reduce } from 'lodash'
|
||||
|
||||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
|
||||
import { StatusCodeError } from 'src/services/errors/errors.js'
|
||||
|
||||
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
|
||||
|
||||
export const createApp = ({ instance }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_name', 'PleromaFE')
|
||||
formData.append('website', 'https://pleroma.social')
|
||||
formData.append('redirect_uris', REDIRECT_URI)
|
||||
formData.append('scopes', 'read write follow push admin')
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/api/v1/apps`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
}).then(({ data, ...rest }) => ({
|
||||
...rest,
|
||||
data: {
|
||||
...data,
|
||||
clientId: data.client_id,
|
||||
clientSecret: data.client_secret,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export const getLoginUrl = ({ instance, clientId }) => {
|
||||
const data = {
|
||||
responseType: 'code',
|
||||
clientId,
|
||||
redirectUri: REDIRECT_URI,
|
||||
scope: 'read write follow push admin',
|
||||
}
|
||||
|
||||
return `${instance}/oauth/authorize${paramsString(data)}`
|
||||
}
|
||||
|
||||
export const getTokenWithCredentials = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
username,
|
||||
password,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'password')
|
||||
formData.append('username', username)
|
||||
formData.append('password', password)
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/token`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const getToken = ({ clientId, clientSecret, instance, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'authorization_code')
|
||||
formData.append('code', code)
|
||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/token`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const getClientToken = ({ clientId, clientSecret, instance }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'client_credentials')
|
||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/token`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
export const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.client_id)
|
||||
formData.append('client_secret', app.client_secret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'totp')
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/mfa/challenge`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.client_id)
|
||||
formData.append('client_secret', app.client_secret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'recovery')
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/mfa/challenge`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const revokeToken = ({ app, instance, token }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.clientId)
|
||||
formData.append('client_secret', app.clientSecret)
|
||||
formData.append('token', token)
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/revoke`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
|
@ -14,9 +14,11 @@ import {
|
|||
import { RegistrationError, StatusCodeError } from 'src/services/errors/errors'
|
||||
|
||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
/* eslint-env browser */
|
||||
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
|
||||
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
|
||||
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
|
||||
`/auth/password${paramsString({ email })}`
|
||||
|
||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
|
||||
const MASTODON_FOLLOWING_URL = (
|
||||
|
|
@ -110,7 +112,7 @@ export const fetchUserByName = ({ name, credentials }) =>
|
|||
credentials,
|
||||
params: { acct: name },
|
||||
})
|
||||
.then((data) => data.id)
|
||||
.then(({ data }) => data.id)
|
||||
.catch((error) => {
|
||||
if (error && error.statusCode === 404) {
|
||||
// Either the backend does not support lookup endpoint,
|
||||
|
|
@ -313,6 +315,13 @@ export const verifyCredentials = ({ credentials }) =>
|
|||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
|
||||
export const resetPassword = ({ instance, email }) => {
|
||||
return promisedRequest({
|
||||
url: MASTODON_PASSWORD_RESET_URL({ email }),
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
export const suggestions = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: SUGGESTIONS_URL,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import oauthApi from '../../services/new_api/oauth.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -35,19 +35,13 @@ const LoginForm = {
|
|||
this.isTokenAuth ? this.submitToken() : this.submitPassword()
|
||||
},
|
||||
submitToken() {
|
||||
const data = {
|
||||
instance: this.server,
|
||||
commit: this.$store.commit,
|
||||
}
|
||||
|
||||
// NOTE: we do not really need the app token, but obtaining a token and
|
||||
// calling verify_credentials is the only way to ensure the app still works.
|
||||
this.ensureAppToken().then(() => {
|
||||
const app = {
|
||||
window.location.href = getLoginUrl({
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
}
|
||||
oauthApi.login({ ...app, ...data })
|
||||
instance: this.server,
|
||||
})
|
||||
})
|
||||
},
|
||||
submitPassword() {
|
||||
|
|
@ -56,37 +50,31 @@ const LoginForm = {
|
|||
// NOTE: we do not really need the app token, but obtaining a token and
|
||||
// calling verify_credentials is the only way to ensure the app still works.
|
||||
this.ensureAppToken().then(() => {
|
||||
const app = {
|
||||
getTokenWithCredentials({
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
}
|
||||
|
||||
oauthApi
|
||||
.getTokenWithCredentials({
|
||||
...app,
|
||||
instance: this.server,
|
||||
username: this.user.username,
|
||||
password: this.user.password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.error) {
|
||||
if (result.error === 'mfa_required') {
|
||||
this.requireMFA({ settings: result })
|
||||
} else if (result.identifier === 'password_reset_required') {
|
||||
this.$router.push({
|
||||
name: 'password-reset',
|
||||
params: { passwordResetRequested: true },
|
||||
})
|
||||
} else {
|
||||
this.error = result.error
|
||||
this.focusOnPasswordInput()
|
||||
}
|
||||
return
|
||||
instance: this.server,
|
||||
username: this.user.username,
|
||||
password: this.user.password,
|
||||
}).then((result) => {
|
||||
if (result.error) {
|
||||
if (result.error === 'mfa_required') {
|
||||
this.requireMFA({ settings: result })
|
||||
} else if (result.identifier === 'password_reset_required') {
|
||||
this.$router.push({
|
||||
name: 'password-reset',
|
||||
params: { passwordResetRequested: true },
|
||||
})
|
||||
} else {
|
||||
this.error = result.error
|
||||
this.focusOnPasswordInput()
|
||||
}
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
return
|
||||
}
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
clearError() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { mapActions, mapState, mapStores } from 'pinia'
|
||||
|
||||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { verifyRecoveryCode } from 'src/api/mfa.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -43,18 +43,18 @@ export default {
|
|||
code: this.code,
|
||||
}
|
||||
|
||||
mfaApi.verifyRecoveryCode(data).then((result) => {
|
||||
if (result.error) {
|
||||
this.error = result.error
|
||||
verifyRecoveryCode(data)
|
||||
.then((result) => {
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
this.code = null
|
||||
this.focusOnCodeInput()
|
||||
return
|
||||
}
|
||||
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { mapActions, mapState, mapStores } from 'pinia'
|
||||
|
||||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { verifyOTPCode } from 'src/api/mfa.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -46,18 +46,18 @@ export default {
|
|||
code: this.code,
|
||||
}
|
||||
|
||||
mfaApi.verifyOTPCode(data).then((result) => {
|
||||
if (result.error) {
|
||||
this.error = result.error
|
||||
verifyOTPCode(data)
|
||||
.then(({ data: result }) => {
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
this.code = null
|
||||
this.focusOnCodeInput()
|
||||
return
|
||||
}
|
||||
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import oauth from '../../services/new_api/oauth.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { getToken } from 'src/api/oauth.js'
|
||||
|
||||
const oac = {
|
||||
props: ['code'],
|
||||
mounted() {
|
||||
|
|
@ -10,18 +10,16 @@ const oac = {
|
|||
const oauthStore = useOAuthStore()
|
||||
const { clientId, clientSecret } = oauthStore
|
||||
|
||||
oauth
|
||||
.getToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance: useInstanceStore().server,
|
||||
code: this.code,
|
||||
})
|
||||
.then((result) => {
|
||||
oauthStore.setToken(result.access_token)
|
||||
this.$store.dispatch('loginUser', result.access_token)
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
getToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance: useInstanceStore().server,
|
||||
code: this.code,
|
||||
}).then(({ data: result }) => {
|
||||
oauthStore.setToken(result.access_token)
|
||||
this.$store.dispatch('loginUser', result.access_token)
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
uniq,
|
||||
} from 'lodash'
|
||||
|
||||
import oauthApi from '../services/new_api/oauth.js'
|
||||
import {
|
||||
registerPushNotifications,
|
||||
unregisterPushNotifications,
|
||||
|
|
@ -30,6 +29,7 @@ import { useOAuthStore } from 'src/stores/oauth.js'
|
|||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
import { revokeToken } from 'src/api/oauth.js'
|
||||
import {
|
||||
fetchFollowers,
|
||||
fetchFriends,
|
||||
|
|
@ -711,7 +711,7 @@ const users = {
|
|||
token: oauth.userToken,
|
||||
}
|
||||
|
||||
return oauthApi.revokeToken(params)
|
||||
return revokeToken(params)
|
||||
})
|
||||
.then(() => {
|
||||
store.commit('clearCurrentUser')
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const fetchRelationship = (attempt, userId, store) =>
|
|||
id: userId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then((relationship) => {
|
||||
.then(({ data: relationship }) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
return relationship
|
||||
})
|
||||
|
|
@ -36,7 +36,7 @@ const fetchRelationship = (attempt, userId, store) =>
|
|||
})
|
||||
|
||||
export const requestFollow = async (userId, store) => {
|
||||
const updated = await followUser({
|
||||
const { data: updated } = await followUser({
|
||||
id: userId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
|
@ -58,7 +58,7 @@ export const requestFollow = async (userId, store) => {
|
|||
}
|
||||
|
||||
export const requestUnfollow = async (userId, store) => {
|
||||
const updated = await unfollowUser({
|
||||
const { data: updated } = await unfollowUser({
|
||||
id: userId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
const verifyOTPCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const url = `${instance}/oauth/mfa/challenge`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', clientId)
|
||||
form.append('client_secret', clientSecret)
|
||||
form.append('mfa_token', mfaToken)
|
||||
form.append('code', code)
|
||||
form.append('challenge_type', 'totp')
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const verifyRecoveryCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const url = `${instance}/oauth/mfa/challenge`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', clientId)
|
||||
form.append('client_secret', clientSecret)
|
||||
form.append('mfa_token', mfaToken)
|
||||
form.append('code', code)
|
||||
form.append('challenge_type', 'recovery')
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const mfa = {
|
||||
verifyOTPCode,
|
||||
verifyRecoveryCode,
|
||||
}
|
||||
|
||||
export default mfa
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import { reduce } from 'lodash'
|
||||
|
||||
import { StatusCodeError } from 'src/services/errors/errors.js'
|
||||
|
||||
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
|
||||
|
||||
export const getJsonOrError = async (response) => {
|
||||
if (response.ok) {
|
||||
return response.json().catch((error) => {
|
||||
throw new StatusCodeError(response.status, error, {}, response)
|
||||
})
|
||||
} else {
|
||||
throw new StatusCodeError(
|
||||
response.status,
|
||||
await response.text(),
|
||||
{},
|
||||
response,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const createApp = (instance) => {
|
||||
const url = `${instance}/api/v1/apps`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_name', 'PleromaFE')
|
||||
form.append('website', 'https://pleroma.social')
|
||||
form.append('redirect_uris', REDIRECT_URI)
|
||||
form.append('scopes', 'read write follow push admin')
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
.then((app) => ({
|
||||
clientId: app.client_id,
|
||||
clientSecret: app.client_secret,
|
||||
}))
|
||||
}
|
||||
|
||||
export const verifyAppToken = ({ instance, appToken }) => {
|
||||
return window
|
||||
.fetch(`${instance}/api/v1/apps/verify_credentials`, {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${appToken}` },
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
}
|
||||
|
||||
const login = ({ instance, clientId }) => {
|
||||
const data = {
|
||||
response_type: 'code',
|
||||
client_id: clientId,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
scope: 'read write follow push admin',
|
||||
}
|
||||
|
||||
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 = `${instance}/oauth/authorize?${dataString}`
|
||||
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
const getTokenWithCredentials = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
username,
|
||||
password,
|
||||
}) => {
|
||||
const url = `${instance}/oauth/token`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', clientId)
|
||||
form.append('client_secret', clientSecret)
|
||||
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 = ({ clientId, clientSecret, instance, code }) => {
|
||||
const url = `${instance}/oauth/token`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', clientId)
|
||||
form.append('client_secret', clientSecret)
|
||||
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())
|
||||
}
|
||||
|
||||
export const getClientToken = ({ clientId, clientSecret, instance }) => {
|
||||
const url = `${instance}/oauth/token`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', clientId)
|
||||
form.append('client_secret', clientSecret)
|
||||
form.append('grant_type', 'client_credentials')
|
||||
form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
}
|
||||
const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
|
||||
const url = `${instance}/oauth/mfa/challenge`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', app.client_id)
|
||||
form.append('client_secret', app.client_secret)
|
||||
form.append('mfa_token', mfaToken)
|
||||
form.append('code', code)
|
||||
form.append('challenge_type', 'totp')
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => {
|
||||
const url = `${instance}/oauth/mfa/challenge`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', app.client_id)
|
||||
form.append('client_secret', app.client_secret)
|
||||
form.append('mfa_token', mfaToken)
|
||||
form.append('code', code)
|
||||
form.append('challenge_type', 'recovery')
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const revokeToken = ({ app, instance, token }) => {
|
||||
const url = `${instance}/oauth/revoke`
|
||||
const form = new window.FormData()
|
||||
|
||||
form.append('client_id', app.clientId)
|
||||
form.append('client_secret', app.clientSecret)
|
||||
form.append('token', token)
|
||||
|
||||
return window
|
||||
.fetch(url, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
}
|
||||
|
||||
const oauth = {
|
||||
login,
|
||||
getToken,
|
||||
getTokenWithCredentials,
|
||||
verifyOTPCode,
|
||||
verifyRecoveryCode,
|
||||
revokeToken,
|
||||
}
|
||||
|
||||
export default oauth
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { reduce } from 'lodash'
|
||||
|
||||
const MASTODON_PASSWORD_RESET_URL = '/auth/password'
|
||||
|
||||
const resetPassword = ({ instance, email }) => {
|
||||
const params = { email }
|
||||
const query = reduce(
|
||||
params,
|
||||
(acc, v, k) => {
|
||||
const encoded = `${k}=${encodeURIComponent(v)}`
|
||||
return `${acc}&${encoded}`
|
||||
},
|
||||
'',
|
||||
)
|
||||
const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}`
|
||||
|
||||
return window.fetch(url, {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
export default resetPassword
|
||||
|
|
@ -2,14 +2,7 @@ import { defineStore } from 'pinia'
|
|||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import {
|
||||
getAnnouncements as adminGetAnnouncements,
|
||||
deleteAnnouncement,
|
||||
editAnnouncement,
|
||||
postAnnouncement,
|
||||
} from 'src/api/admin.js'
|
||||
import { getAnnouncements } from 'src/api/public.js'
|
||||
import { dismissAnnouncement } from 'src/api/user.js'
|
||||
|
||||
const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
|
||||
|
||||
|
|
@ -18,6 +11,8 @@ export const useAnnouncementsStore = defineStore('announcements', {
|
|||
announcements: [],
|
||||
supportsAnnouncements: true,
|
||||
fetchAnnouncementsTimer: undefined,
|
||||
adminActions: {},
|
||||
userActions: {},
|
||||
}),
|
||||
getters: {
|
||||
unreadAnnouncementCount() {
|
||||
|
|
@ -32,34 +27,41 @@ export const useAnnouncementsStore = defineStore('announcements', {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
fetchAnnouncements() {
|
||||
if (!this.supportsAnnouncements) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
async fetchAnnouncements() {
|
||||
if (!this.supportsAnnouncements) return
|
||||
|
||||
const currentUser = window.vuex.state.users.currentUser
|
||||
const isAdmin =
|
||||
currentUser &&
|
||||
currentUser.privileges.has('announcements_manage_announcements')
|
||||
|
||||
const fetchAnnouncements = async () => {
|
||||
if (!isAdmin) {
|
||||
const result = await getAnnouncements({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
return result.data
|
||||
try {
|
||||
if (currentUser) {
|
||||
this.userActions = await import('src/api/user.js')
|
||||
}
|
||||
|
||||
const { data: all } = await adminGetAnnouncements({
|
||||
if (isAdmin) {
|
||||
this.adminActions = await import('src/api/admin.js')
|
||||
} else {
|
||||
const all = await getAnnouncements({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
return all.data
|
||||
}
|
||||
|
||||
const { data: all } = await this.adminActions.getAnnouncements({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
const { data: visible } = await getAnnouncements({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
const visibleObject = visible.reduce((a, c) => {
|
||||
a[c.id] = c
|
||||
return a
|
||||
}, {})
|
||||
|
||||
const getWithinVisible = (announcement) =>
|
||||
visibleObject[announcement.id]
|
||||
|
||||
|
|
@ -72,36 +74,32 @@ export const useAnnouncementsStore = defineStore('announcements', {
|
|||
}
|
||||
})
|
||||
|
||||
return all
|
||||
this.announcements = all
|
||||
} catch (error) {
|
||||
// If and only if backend does not support announcements, it would return 404.
|
||||
// In this case, silently ignores it.
|
||||
if (error && error.statusCode === 404) {
|
||||
this.supportsAnnouncements = false
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return fetchAnnouncements()
|
||||
.then((announcements) => {
|
||||
this.announcements = announcements
|
||||
})
|
||||
.catch((error) => {
|
||||
// If and only if backend does not support announcements, it would return 404.
|
||||
// In this case, silently ignores it.
|
||||
if (error && error.statusCode === 404) {
|
||||
this.supportsAnnouncements = false
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
},
|
||||
markAnnouncementAsRead(id) {
|
||||
return dismissAnnouncement({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
const index = this.announcements.findIndex((a) => a.id === id)
|
||||
return this.userActions
|
||||
.dismissAnnouncement({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
const index = this.announcements.findIndex((a) => a.id === id)
|
||||
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.announcements[index].read = true
|
||||
})
|
||||
this.announcements[index].read = true
|
||||
})
|
||||
},
|
||||
startFetchingAnnouncements() {
|
||||
if (this.fetchAnnouncementsTimer) {
|
||||
|
|
@ -122,35 +120,41 @@ export const useAnnouncementsStore = defineStore('announcements', {
|
|||
clearInterval(interval)
|
||||
},
|
||||
postAnnouncement({ content, startsAt, endsAt, allDay }) {
|
||||
return postAnnouncement({
|
||||
credentials: useOAuthStore().token,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
}).then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
return this.adminActions
|
||||
.postAnnouncement({
|
||||
credentials: useOAuthStore().token,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
})
|
||||
.then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
},
|
||||
editAnnouncement({ id, content, startsAt, endsAt, allDay }) {
|
||||
return editAnnouncement({
|
||||
id,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
return this.adminActions
|
||||
.editAnnouncement({
|
||||
id,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
},
|
||||
deleteAnnouncement(id) {
|
||||
return deleteAnnouncement({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
return this.adminActions
|
||||
.deleteAnnouncement({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
return this.fetchAnnouncements()
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,11 +2,8 @@ import { defineStore } from 'pinia'
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
||||
import {
|
||||
createApp,
|
||||
getClientToken,
|
||||
verifyAppToken,
|
||||
} from 'src/services/new_api/oauth.js'
|
||||
import { createApp, getClientToken } from 'src/api/oauth.js'
|
||||
import { verifyCredentials } from 'src/api/public.js'
|
||||
|
||||
// status codes about verifyAppToken (GET /api/v1/apps/verify_credentials)
|
||||
const isAppTokenRejected = (error) =>
|
||||
|
|
@ -61,9 +58,9 @@ export const useOAuthStore = defineStore('oauth', {
|
|||
},
|
||||
async createApp() {
|
||||
const instance = useInstanceStore().server
|
||||
const app = await createApp(instance)
|
||||
this.setClientData(app)
|
||||
return app
|
||||
const app = await createApp({ instance })
|
||||
this.setClientData(app.data)
|
||||
return app.data
|
||||
},
|
||||
/// Use this if you want to get the client id and secret but are not interested
|
||||
/// in whether they are valid.
|
||||
|
|
@ -85,7 +82,7 @@ export const useOAuthStore = defineStore('oauth', {
|
|||
clientSecret: this.clientSecret,
|
||||
instance,
|
||||
})
|
||||
this.setAppToken(res.access_token)
|
||||
this.setAppToken(res.data.access_token)
|
||||
return res.access_token
|
||||
},
|
||||
/// Use this if you want to ensure the app is still valid to use.
|
||||
|
|
@ -93,9 +90,8 @@ export const useOAuthStore = defineStore('oauth', {
|
|||
async ensureAppToken() {
|
||||
if (this.appToken) {
|
||||
try {
|
||||
await verifyAppToken({
|
||||
instance: useInstanceStore().server,
|
||||
appToken: this.appToken,
|
||||
await verifyCredentials({
|
||||
credentials: this.appToken,
|
||||
})
|
||||
return this.appToken
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue