Skip to content

Commit

Permalink
Notion-Auth
Browse files Browse the repository at this point in the history
  • Loading branch information
tangly1024 committed Jul 10, 2024
1 parent da90c17 commit ccf0c18
Show file tree
Hide file tree
Showing 11 changed files with 1,247 additions and 406 deletions.
1 change: 1 addition & 0 deletions blog.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const BLOG = {
'/[prefix]': 'LayoutSlug',
'/[prefix]/[slug]': 'LayoutSlug',
'/[prefix]/[slug]/[...suffix]': 'LayoutSlug',
'/auth/result': 'LayoutAuth',
'/sign-in/[[...index]]': 'LayoutSignIn',
'/sign-up/[[...index]]': 'LayoutSignUp'
},
Expand Down
5 changes: 5 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"jsx": "react",
"allowJs": true,
"checkJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
Expand Down
119 changes: 119 additions & 0 deletions lib/notion/CustomNotionApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const axios = require('axios')

// 发送 Notion API 请求
async function postNotion(properties, databaseId, listContentMain, token) {
const url = 'https://api.notion.com/v1/pages'

const children = listContentMain
.map(contentMain => {
if (contentMain.type === 'paragraph') {
return {
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [
{ type: 'text', text: { content: contentMain.content } }
]
}
}
} else if (['file', 'image'].includes(contentMain.type)) {
return {
object: 'block',
type: contentMain.type,
[contentMain.type]: {
type: 'external',
external: { url: contentMain.content }
}
}
}
return null
})
.filter(Boolean)

const payload = {
parent: { database_id: databaseId },
properties,
children
}

const headers = {
accept: 'application/json',
'Notion-Version': '2022-06-28',
'content-type': 'application/json',
Authorization: `Bearer ${token}`
}

try {
const response = await axios.post(url, payload, { headers })
return response
} catch (error) {
console.error('写入Notion异常', error)
throw new Error(`Error posting to Notion: ${error.message}`)
}
}

// 处理响应结果
function responseResult(response) {
if (response.status === 200) {
console.log('成功...')
console.log(response.data)
} else {
console.log('失败...')
console.log(response.data)
}
}

// 准备属性字段
function notionProperty(id, avatar, name, mail, lastLoginTime, token) {
return {
id: {
rich_text: [
{
type: 'text',
text: {
content: id,
link: null
}
}
]
},
avatar: {
files: [
{
name: 'Project Alpha blueprint',
external: {
url: avatar
}
}
]
},
name: {
title: [
{
text: {
content: name
}
}
]
},
mail: {
email: mail
},
last_login_time: {
date: {
start: lastLoginTime
}
},
token: {
rich_text: [
{
type: 'text',
text: {
content: token,
link: null
}
}
]
}
}
}
61 changes: 39 additions & 22 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,57 @@
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

/**
* clerk 身份验证中间件
* Clerk 身份验证中间件
*/
export const config = {
// 这里设置白名单,防止静态资源被拦截
matcher: ['/((?!.*\\..*|_next|/sign-in).*)', '/', '/(api|trpc)(.*)']
matcher: ['/((?!.*\\..*|_next|/sign-in|/auth).*)', '/', '/(api|trpc)(.*)']
}

// 被保护的路由

// 限制登录访问的路由
const isTenantRoute = createRouteMatcher([
'/user/organization-selector(.*)',
'/user/orgid/(.*)'
])

// 被限制权限的路由
// 限制权限访问的路由
const isTenantAdminRoute = createRouteMatcher([
'/admin/(.*)/memberships',
'/admin/(.*)/domain'
])

// 路由登录及权限检查
export default clerkMiddleware(
(auth, req) => {
// Restrict admin routes to users with specific permissions
if (isTenantAdminRoute(req)) {
auth().protect(has => {
return (
has({ permission: 'org:sys_memberships:manage' }) ||
has({ permission: 'org:sys_domains_manage' })
)
})
}
// Restrict organization routes to signed in users
if (isTenantRoute(req)) auth().protect()
}
// { debug: process.env.npm_lifecycle_event === 'dev' } // 开发调试模式打印日志
)
/**
* 没有配置权限相关功能的返回
* @param req
* @param ev
* @returns
*/
const noAuthMiddleware = async (req, ev) => {
// 如果没有配置 Clerk 相关环境变量,返回一个默认响应或者继续处理请求
return NextResponse.next()
}

/**
* 鉴权中间件
*/
const authMiddleware = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
? clerkMiddleware(
(auth, req) => {
// 限制管理员路由访问权限
if (isTenantAdminRoute(req)) {
auth().protect(has => {
return (
has({ permission: 'org:sys_memberships:manage' }) ||
has({ permission: 'org:sys_domains_manage' })
)
})
}
// 限制组织路由访问权限
if (isTenantRoute(req)) auth().protect()
}
// { debug: process.env.npm_lifecycle_event === 'dev' } // 开发调试模式打印日志
)
: noAuthMiddleware

export default authMiddleware
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@next/bundle-analyzer": "^12.1.1",
"@vercel/analytics": "^1.0.0",
"algoliasearch": "^4.18.0",
"axios": "^1.7.2",
"feed": "^4.2.2",
"js-md5": "^0.7.3",
"lodash.throttle": "^4.1.1",
Expand All @@ -43,25 +44,27 @@
"react-tweet-embed": "~2.0.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react": "18.3.3",
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.16.0",
"@waline/client": "^2.5.1",
"autoprefixer": "^10.4.13",
"cross-env": "^7.0.3",
"eslint": "^7.26.0",
"eslint": "^9.6.0",
"eslint-config-next": "^13.1.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.23.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.2",
"next-sitemap": "^1.6.203",
"postcss": "^8.4.31",
"prettier": "3.2.5",
"prettier": "^3.3.2",
"tailwindcss": "^3.3.2",
"typescript": "^5.4.5",
"typescript": "5.5.3",
"webpack-bundle-analyzer": "^4.5.0"
},
"resolutions": {
Expand Down
115 changes: 115 additions & 0 deletions pages/api/auth/callback/notion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// pages/api/auth.js
import axios from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next'

/**
* Notion授权返回结果
*/
export interface NotionTokenResponseData {
access_token: string
token_type: string
bot_id: string
workspace_name: string
workspace_icon: string
workspace_id: string
owner: {
type: string
user: {
object: string
id: string
name: string
avatar_url: string
type: string
person: {
email: string
}
}
}
duplicated_template_id: string | null
request_id: string
}

export interface NotionTokenResponse {
status: number
statusText: string
data: NotionTokenResponseData
}

/**
* Notion授权回调
* @param req
* @param res
* @returns
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const code = Array.isArray(req.query.code)
? req.query.code[0]
: req.query.code

if (!code) {
return res.status(400).json({ error: 'Invalid request, code is missing' })
}

const params = await fetchToken(code)

if (params?.status === 200) {
const redirectQuery = {
msg: '成功了' + JSON.stringify(params.data)
}

// 这里将用户数据写入到Notion数据库
res.redirect(302, `/auth/result?${new URLSearchParams(redirectQuery)}`)
} else {
const redirectQuery = { msg: params?.statusText || '请求异常' }
res.redirect(
302,
`/auth/result?${new URLSearchParams(redirectQuery).toString()}`
)
}
} catch (error) {
console.error(error)
res.status(500).json({ error: 'Internal Server Error' })
}
}
/**
* 获取token
* @param code
* @returns
*/
const fetchToken = async (code: string): Promise<NotionTokenResponse> => {
const clientId = process.env.OAUTH_CLIENT_ID
const clientSecret = process.env.OAUTH_CLIENT_SECRET
const redirectUri = process.env.OAUTH_REDIRECT_URI
const encoded = Buffer.from(`${clientId}:${clientSecret}`).toString('base64')

try {
const response = await axios.post<NotionTokenResponseData>(
'https://api.notion.com/v1/oauth/token',
{
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri
},
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Basic ${encoded}`
}
}
)
console.log('OAuth身份信息', response.data)
return {
status: response.status,
statusText: response.statusText,
data: response.data
}
} catch (error) {
console.error('Error fetching token', error)
return null
}
}
Loading

0 comments on commit ccf0c18

Please sign in to comment.