Skip to content

Commit

Permalink
[*] Complete OAuth2.0 authorize page
Browse files Browse the repository at this point in the history
  • Loading branch information
Muska-Ami committed Dec 3, 2024
1 parent 2f3c6f1 commit b385fe4
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 5 deletions.
5 changes: 5 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ async function fetchUserInfo() {
watch(
() => route.meta,
(value) => {
if (value.noSidebar) {
showMainSidebar.value = false
showGuestSidebar.value = false
return
}
if (value.needLogin) {
showMainSidebar.value = true
showGuestSidebar.value = false
Expand Down
5 changes: 5 additions & 0 deletions src/api/v2/app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import info from './info.api'

export default {
info: info
}
13 changes: 13 additions & 0 deletions src/api/v2/app/info.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//@ts-ignore
import { get } from '@/utils/request'
import base from '@/api/base'

const info = async (user_id: number, app_id: number) => {
const rs = get(`${base.api_v2_url}/app/info`, {
user_id: user_id,
app_id: app_id,
})
return base.buildResponse(await rs)
}

export default info
18 changes: 18 additions & 0 deletions src/api/v2/auth/oauth/authorize.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//@ts-ignore
import { post } from '@/utils/request'
import base from '@/api/base'

const authorize = async (
user_id: number,
app_id: number,
request_permission_ids: Array<number>,
) => {
const rs = post(`${base.api_v2_url}/auth/oauth/authorize`, {
user_id: user_id,
app_id: app_id,
request_permission_ids: request_permission_ids
})
return base.buildResponse(await rs)
}

export default authorize
6 changes: 5 additions & 1 deletion src/api/v2/auth/oauth/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import qq from './qq'
import permission from './permission'
import authorize from './authorize.api'

export default {
qq: qq
qq: qq,
permission: permission,
authorize: authorize
}
12 changes: 12 additions & 0 deletions src/api/v2/auth/oauth/permission/all.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ts-ignore
import { get } from '@/utils/request'
import base from '@/api/base'

const all = async (user_id: number) => {
const rs = get(`${base.api_v2_url}/auth/oauth/permission/all`, {
user_id: user_id,
})
return base.buildResponse(await rs)
}

export default all
5 changes: 5 additions & 0 deletions src/api/v2/auth/oauth/permission/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import all from './all.api'

export default {
all: all
}
4 changes: 3 additions & 1 deletion src/api/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import user from './user'
import email from './email'
import notice from './notice'
import minecraft from './minecraft'
import app from './app'

export default {
user: user,
Expand All @@ -21,5 +22,6 @@ export default {
sign: sign,
icp: icp,
notice: notice,
minecraft: minecraft
minecraft: minecraft,
app: app
}
25 changes: 25 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ const routes = [
}
},
component: () => import('@views/auth/ResetPasswordView.vue')
},
{
path: 'oauth',
children: [
{
path: 'authorize',
name: 'OAuthAuthorize',
meta: {
title: 'OAuth 授权界面',
needLogin: true,
noSidebar: true
},
component: () => import('@views/auth/oauth/AppAuthView.vue')
}
]
}
]
},
Expand Down Expand Up @@ -160,6 +175,16 @@ const routes = [
},
component: () => import('@views/IcpCheckView.vue')
},
{
path: '/app',
name: 'App',
meta: {
title: '应用',
keepAlive: true,
needLogin: true
},
component: () => import('@views/AppView.vue')
},
{
path: '/games',
children: [
Expand Down
4 changes: 1 addition & 3 deletions src/utils/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ const instance = axios.create({

const tokenDomains = ['api.locyanfrp.cn', 'api-v2.locyanfrp.cn', 'localhost']

// post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置,即设置post的请求头为
axios.defaults.headers.post['Content-Type'] = 'multipart/form-data;charset=UTF-8'
// 添加请求拦截器
instance.interceptors.request.use(
async (config) => {
Expand Down Expand Up @@ -119,7 +117,7 @@ export async function get(url, params) {
* @param headers
*/
export async function post(url, params, headers = {}) {
return await instance.post(url, QS.stringify(params), {
return await instance.post(url, QS.stringify(params, { arrayFormat: 'repeat' }), {
headers: { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
Expand Down
3 changes: 3 additions & 0 deletions src/views/AppView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<!-- TODO: 用户应用管理界面 -->
</template>
208 changes: 208 additions & 0 deletions src/views/auth/oauth/AppAuthView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<template>
<div class="flex-center outbox">
<n-h2>应用授权</n-h2>
<n-spin style="width: 100%; max-width: 500px" :show="loading">
<n-card>
<div v-if="valid">
<n-h3>{{ applicationName }}</n-h3>
<n-text>{{ applicationDescription }}</n-text>
<n-divider></n-divider>
<n-text>确认是否要授权此应用访问您的数据?这将授予应用获取以下权限:</n-text>
<div>
<n-ul>
<n-li v-for="permission in applicationPermissionRequested">
<n-text>{{ permission.node }}</n-text>
<n-text style="margin-left: 0.3rem">{{ permission.description }}</n-text>
</n-li>
</n-ul>
</div>
<n-divider></n-divider>
<div class="buttons">
<n-tooltip trigger="hover">
<template #trigger>
<n-avatar :src="userAvatarUrl" round></n-avatar>
</template>
将以此身份继续: {{ username }}
</n-tooltip>
<n-button
:loading="acceptLoading"
:disabled="acceptLoading"
type="primary"
@click="doAuthorize"
>同意</n-button>
<n-button
:loading="denyLoading"
:disabled="denyLoading"
@click="deny"
>拒绝</n-button>
</div>
<n-divider></n-divider>
<div style="text-align: center;">
<n-text>
授权后,您将被重定向到以下地址:
<br>
{{ urlKeys.redirectUrl }}
</n-text>
</div>
</div>
<div v-else>
<n-text style="color: red;">无效请求,请检查 URL 参数</n-text>
</div>
</n-card>
</n-spin>
</div>
</template>

<style scoped>
.outbox {
margin: 3rem;
}
.flex-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.buttons .n-button {
margin-left: 1rem;
float: right;
}
</style>

<script setup lang="ts">
import userData from '@/utils/stores/userData/store'
import { getUrlKey } from '@/utils/request'
import { onMounted, ref } from 'vue'
import { sendSuccessMessage, sendErrorMessage } from '@/utils/message'
import api from '@/api'
import logger from '@/utils/logger'
const loading = ref(true)
const valid = ref(true)
const urlKeys = {
appId: getUrlKey('app_id'),
scopes: getUrlKey('scopes'),
redirectUrl: getUrlKey('redirect_url')
}
if (urlKeys.appId == null || urlKeys.scopes == null || urlKeys.redirectUrl == null) {
loading.value = false
valid.value = false
}
const applicationName = ref('')
const applicationDescription = ref('')
const applicationPermissionRequested = ref([])
const username = ref('')
const userAvatarUrl = ref('')
username.value = userData.getters.get_username
userAvatarUrl.value = userData.getters.get_avatar
userData.subscribe((mutation: any, state: any) => {
if (mutation.type === 'set_avatar' || mutation.type === 'set_user_info') {
userAvatarUrl.value = state.avatar
}
})
const acceptLoading = ref(false)
const denyLoading = ref(false)
async function doAuthorize() {
acceptLoading.value = true;
let permissionIds: Array<number> = []
applicationPermissionRequested.value.forEach(permission => {
permissionIds.push(permission.id)
})
let rs
try {
rs = await api.v2.auth.oauth.authorize(userData.getters.get_user_id, urlKeys.appId, permissionIds)
} catch (e) {
logger.error(e)
sendErrorMessage("授权失败: " + e)
acceptLoading.value = false
return
}
if (rs != null) {
if (rs.status === 200) {
sendSuccessMessage("授权成功,正在重定向,请不要刷新浏览器")
window.location.href = `${urlKeys.redirectUrl}?refresh_token=${rs.data.refresh_token}`
} else {
sendErrorMessage("授权失败: " + rs.message)
}
}
acceptLoading.value = false
}
function deny() {
denyLoading.value = true;
window.location.href = urlKeys.redirectUrl + "?error=User.Deny";
}
onMounted(async () => {
let permissionList
async function initAppInfo(): Promise<boolean> {
let rs
try {
rs = await api.v2.app.info(userData.getters.get_user_id, urlKeys.appId)
} catch (e) {
logger.error(e)
sendErrorMessage(e)
return false
}
if (rs != null) {
if (rs.status === 200) {
applicationName.value = rs.data.name
applicationDescription.value = rs.data.description
return true
} else if (rs.status === 404) {
sendErrorMessage("未找到此应用程序")
valid.value = false
return true
} else {
sendErrorMessage(rs.message)
}
}
return false
}
async function initPermissions(): Promise<boolean> {
let rs
try {
rs = await api.v2.auth.oauth.permission.all(userData.getters.get_user_id)
} catch (e) {
logger.error(e)
sendErrorMessage(e)
return false
}
if (rs != null) {
if (rs.status === 200) {
// logger.info(rs.data)
permissionList = rs.data.list
return true
} else {
sendErrorMessage(rs.message)
}
}
return false
}
async function initAppPermissions(): Promise<boolean> {
const permissions = urlKeys.scopes.split(',')
permissionList.forEach(permission => {
if (permissions.includes(permission.node)) applicationPermissionRequested.value.push(permission)
})
return true
// applicationPermissionRequested
}
let init1 = await initAppInfo()
let init2 = await initPermissions()
let init3 = await initAppPermissions()
if (init1 && init2 && init3) {
loading.value = false
}
})
</script>

0 comments on commit b385fe4

Please sign in to comment.