From 38d24146fa8021d4c59a41d20ca7e1bf16ada57f Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 03:44:03 +0800
Subject: [PATCH 01/14] oauth inside main project, with public client id and
secret
---
components/FileListing.tsx | 8 +
config/api.json | 4 +-
next.config.js | 2 +
package-lock.json | 122 ++++++++-
package.json | 3 +
pages/_app.tsx | 12 +-
pages/api/index.ts | 253 ++++++++++---------
pages/onedrive-vercel-index-oauth/step-1.tsx | 129 ++++++++++
pages/onedrive-vercel-index-oauth/step-2.tsx | 103 ++++++++
pages/onedrive-vercel-index-oauth/step-3.tsx | 148 +++++++++++
tailwind.config.js | 4 +-
utils/accessTokenHandler.ts | 85 +++++++
12 files changed, 749 insertions(+), 124 deletions(-)
create mode 100644 pages/onedrive-vercel-index-oauth/step-1.tsx
create mode 100644 pages/onedrive-vercel-index-oauth/step-2.tsx
create mode 100644 pages/onedrive-vercel-index-oauth/step-3.tsx
create mode 100644 utils/accessTokenHandler.ts
diff --git a/components/FileListing.tsx b/components/FileListing.tsx
index 74a25d6..889a282 100644
--- a/components/FileListing.tsx
+++ b/components/FileListing.tsx
@@ -179,6 +179,14 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
const { data, error, size, setSize } = useProtectedSWRInfinite(path)
if (error) {
+ console.log(error)
+
+ // If error includes 403 which means the user has not completed initial setup, redirect to OAuth page
+ if (error.message.includes('403')) {
+ router.push('/onedrive-vercel-index-oauth/step-1')
+ return
+ }
+
return (
{error.message.includes('401') ?
:
}
diff --git a/config/api.json b/config/api.json
index bd4348e..5414703 100644
--- a/config/api.json
+++ b/config/api.json
@@ -1,7 +1,9 @@
{
"clientId": "d87bcc39-1750-4ca0-ad54-f8d0efbb2735",
+ "obfuscatedClientSecret": "U2FsdGVkX1830zo3/pFDqaBCVBb37iLw3WnBDWGF9GIB2f4apzv0roemp8Y+iIxI3Ih5ecyukqELQEGzZlYiWg==",
"redirectUri": "http://localhost",
"base": "/Public",
"authApi": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
- "driveApi": "https://graph.microsoft.com/v1.0/me/drive"
+ "driveApi": "https://graph.microsoft.com/v1.0/me/drive",
+ "scope": "Files.Read.All Files.ReadWrite.All offline_access"
}
diff --git a/next.config.js b/next.config.js
index 0f1844e..dfd9420 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,7 @@
module.exports = {
webpack: (config) => {
+ config.resolve.fallback = { fs: false, path: false, stream: false, constants: false };
+
// load worker files as a urls with `file-loader`
config.module.rules.unshift({
test: /pdf\.worker\.(min\.)?js/,
diff --git a/package-lock.json b/package-lock.json
index 2473d4f..00b9f86 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,8 @@
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
"jszip": "^3.7.1",
+ "keyv": "^4.0.4",
+ "keyv-file": "^0.2.0",
"next": "^12.0.7",
"nextjs-progressbar": "^0.0.13",
"preview-office-docs": "^1.0.2",
@@ -39,6 +41,7 @@
},
"devDependencies": {
"@types/crypto-js": "^4.0.2",
+ "@types/keyv": "^3.1.3",
"@types/prismjs": "^1.16.5",
"@types/react": "17.0.11",
"@types/react-copy-to-clipboard": "^5.0.0",
@@ -1132,6 +1135,15 @@
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
},
+ "node_modules/@types/keyv": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz",
+ "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/localforage": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.34.tgz",
@@ -3273,6 +3285,16 @@
"url": "https://www.patreon.com/infusion"
}
},
+ "node_modules/fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -4435,6 +4457,11 @@
"node": ">=4"
}
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+ },
"node_modules/json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -4474,6 +4501,14 @@
"node": ">=6"
}
},
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@@ -4536,6 +4571,24 @@
"katex": "cli.js"
}
},
+ "node_modules/keyv": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz",
+ "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/keyv-file": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz",
+ "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^4.0.1",
+ "tslib": "^1.9.3"
+ }
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
@@ -7390,8 +7443,7 @@
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "dev": true
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -7600,6 +7652,14 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -8643,6 +8703,15 @@
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
},
+ "@types/keyv": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz",
+ "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/localforage": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.34.tgz",
@@ -10308,6 +10377,16 @@
"integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==",
"dev": true
},
+ "fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -11107,6 +11186,11 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"peer": true
},
+ "json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+ },
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -11140,6 +11224,14 @@
"minimist": "^1.2.5"
}
},
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@@ -11198,6 +11290,24 @@
"commander": "^6.0.0"
}
},
+ "keyv": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz",
+ "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==",
+ "requires": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "keyv-file": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz",
+ "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==",
+ "requires": {
+ "debug": "^4.1.1",
+ "fs-extra": "^4.0.1",
+ "tslib": "^1.9.3"
+ }
+ },
"language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
@@ -13341,8 +13451,7 @@
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "dev": true
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"tsutils": {
"version": "3.21.0",
@@ -13484,6 +13593,11 @@
}
}
},
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/package.json b/package.json
index b05b829..a34a02c 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,8 @@
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
"jszip": "^3.7.1",
+ "keyv": "^4.0.4",
+ "keyv-file": "^0.2.0",
"next": "^12.0.7",
"nextjs-progressbar": "^0.0.13",
"preview-office-docs": "^1.0.2",
@@ -40,6 +42,7 @@
},
"devDependencies": {
"@types/crypto-js": "^4.0.2",
+ "@types/keyv": "^3.1.3",
"@types/prismjs": "^1.16.5",
"@types/react": "17.0.11",
"@types/react-copy-to-clipboard": "^5.0.0",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index d4c1290..98fa744 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -20,6 +20,7 @@ import {
faArrowAltCircleDown,
faTrashAlt,
faEnvelope,
+ faCheckCircle,
} from '@fortawesome/free-regular-svg-icons'
import {
faPlus,
@@ -36,6 +37,10 @@ import {
faSignOutAlt,
faCloud,
faChevronCircleDown,
+ faExternalLinkAlt,
+ faExclamationCircle,
+ faExclamationTriangle,
+ faHome,
} from '@fortawesome/free-solid-svg-icons'
import * as Icons from '@fortawesome/free-brands-svg-icons'
@@ -78,7 +83,12 @@ library.add(
faEnvelope,
faCloud,
faChevronCircleDown,
- ...iconList,
+ faExternalLinkAlt,
+ faExclamationCircle,
+ faExclamationTriangle,
+ faHome,
+ faCheckCircle,
+ ...iconList
)
function MyApp({ Component, pageProps }: AppProps) {
diff --git a/pages/api/index.ts b/pages/api/index.ts
index 64ad414..77609a5 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -1,9 +1,13 @@
-import axios from 'axios'
-import type { NextApiRequest, NextApiResponse } from 'next'
import { posix as pathPosix } from 'path'
+import type { NextApiRequest, NextApiResponse } from 'next'
+import axios from 'axios'
+import Keyv from 'keyv'
+import { KeyvFile } from 'keyv-file'
+
import apiConfig from '../../config/api.json'
import siteConfig from '../../config/site.json'
+import { revealObfuscatedToken } from '../../utils/accessTokenHandler'
import { compareHashedToken } from '../../utils/protectedRouteHandler'
const basePath = pathPosix.resolve('/', apiConfig.base)
@@ -16,19 +20,28 @@ const encodePath = (path: string) => {
return `:${encodeURIComponent(encodedPath)}`
}
+const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
+
// Store access token in memory, cuz Vercel doesn't provide key-value storage natively
let _access_token = ''
+let _refresh_token = ''
const getAccessToken = async () => {
if (_access_token) {
- console.log('Fetch token from memory.')
+ console.log('Fetch access token from memory.')
return _access_token
}
+ // Return if refresh_token is empty
+ if (!_refresh_token) {
+ console.log('No refresh token, return empty access token.')
+ return ''
+ }
+
const body = new URLSearchParams()
body.append('client_id', apiConfig.clientId)
body.append('redirect_uri', apiConfig.redirectUri)
- body.append('client_secret', process.env.CLIENT_SECRET ? process.env.CLIENT_SECRET : '')
- body.append('refresh_token', process.env.REFRESH_TOKEN ? process.env.REFRESH_TOKEN : '')
+ body.append('client_secret', clientSecret)
+ body.append('refresh_token', _refresh_token)
body.append('grant_type', 'refresh_token')
const resp = await axios.post(apiConfig.authApi, body, {
@@ -45,124 +58,130 @@ const getAccessToken = async () => {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { path = '/', raw = false, next = '' } = req.query
+
+ // Sometimes the path parameter is defaulted to '[...path]' which we need to handle
if (path === '[...path]') {
res.status(400).json({ error: 'No path specified.' })
return
}
-
- if (typeof path === 'string') {
- const accessToken = await getAccessToken()
-
- // Handle authentication through .password
- const protectedRoutes = siteConfig.protectedRoutes
- let authTokenPath = ''
- for (const r of protectedRoutes) {
- if (path.startsWith(r)) {
- authTokenPath = `${r}/.password`
- break
- }
- }
-
- // Fetch password from remote file content
- if (authTokenPath !== '') {
- try {
- const token = await axios.get(`${apiConfig.driveApi}/root${encodePath(authTokenPath)}`, {
- headers: { Authorization: `Bearer ${accessToken}` },
- params: {
- select: '@microsoft.graph.downloadUrl,file',
- },
- })
-
- // Handle request and check for header 'od-protected-token'
- const odProtectedToken = await axios.get(token.data['@microsoft.graph.downloadUrl'])
- // console.log(req.headers['od-protected-token'], odProtectedToken.data.trim())
-
- if (
- !compareHashedToken({
- odTokenHeader: req.headers['od-protected-token'] as string,
- dotPassword: odProtectedToken.data,
- })
- ) {
- res.status(401).json({ error: 'Password required for this folder.' })
- return
- }
- } catch (error: any) {
- // Password file not found, fallback to 404
- if (error.response.status === 404) {
- res.status(404).json({ error: "You didn't set a password for your protected folder." })
- }
- res.status(500).end()
- return
- }
- }
-
- const requestPath = encodePath(path)
- // Handle response from OneDrive API
- const requestUrl = `${apiConfig.driveApi}/root${requestPath}`
- // Whether path is root, which requires some special treatment
- const isRoot = requestPath === ''
-
- // Go for file raw download link and query with only temporary link parameter
- if (raw) {
- const { data } = await axios.get(requestUrl, {
- headers: { Authorization: `Bearer ${accessToken}` },
- params: {
- select: '@microsoft.graph.downloadUrl,folder,file',
- },
- })
-
- if ('folder' in data) {
- res.status(400).json({ error: "Folders doesn't have raw download urls." })
- return
- }
- if ('file' in data) {
- res.redirect(data['@microsoft.graph.downloadUrl'])
- return
- }
- }
-
- // Querying current path identity (file or folder) and follow up query childrens in folder
- // console.log(accessToken)
-
- const { data: identityData } = await axios.get(requestUrl, {
- headers: { Authorization: `Bearer ${accessToken}` },
- params: {
- select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
- },
- })
-
- if ('folder' in identityData) {
- const { data: folderData } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/children`, {
- headers: { Authorization: `Bearer ${accessToken}` },
- params: next
- ? {
- select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
- top: siteConfig.maxItems,
- $skipToken: next,
- }
- : {
- select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
- top: siteConfig.maxItems,
- },
- })
-
- // Extract next page token from full @odata.nextLink
- const nextPage = folderData['@odata.nextLink']
- ? folderData['@odata.nextLink'].match(/&\$skiptoken=(.+)/i)[1]
- : null
-
- // Return paging token if specified
- if (nextPage) {
- res.status(200).json({ folder: folderData, next: nextPage })
- } else {
- res.status(200).json({ folder: folderData })
- }
- return
- }
- res.status(200).json({ file: identityData })
+ // If the path is not a valid path, return 400
+ if (typeof path !== 'string') {
+ res.status(400).json({ error: 'Path query invalid.' })
return
}
- res.status(404).json({ error: 'Path query invalid.' })
+ const accessToken = await getAccessToken()
+
+ // Return error 403 if access_token is empty
+ if (!accessToken) {
+ res.status(403).json({ error: 'No access token.' })
+ return
+ }
+
+ // Handle authentication through .password
+ const protectedRoutes = siteConfig.protectedRoutes
+ let authTokenPath = ''
+ for (const r of protectedRoutes) {
+ if (path.startsWith(r)) {
+ authTokenPath = `${r}/.password`
+ break
+ }
+ }
+
+ // Fetch password from remote file content
+ if (authTokenPath !== '') {
+ try {
+ const token = await axios.get(`${apiConfig.driveApi}/root${encodePath(authTokenPath)}`, {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ params: {
+ select: '@microsoft.graph.downloadUrl,file',
+ },
+ })
+
+ // Handle request and check for header 'od-protected-token'
+ const odProtectedToken = await axios.get(token.data['@microsoft.graph.downloadUrl'])
+ // console.log(req.headers['od-protected-token'], odProtectedToken.data.trim())
+
+ if (
+ !compareHashedToken({
+ odTokenHeader: req.headers['od-protected-token'] as string,
+ dotPassword: odProtectedToken.data,
+ })
+ ) {
+ res.status(401).json({ error: 'Password required for this folder.' })
+ return
+ }
+ } catch (error: any) {
+ // Password file not found, fallback to 404
+ if (error.response.status === 404) {
+ res.status(404).json({ error: "You didn't set a password for your protected folder." })
+ }
+ res.status(500).end()
+ return
+ }
+ }
+
+ const requestPath = encodePath(path)
+ // Handle response from OneDrive API
+ const requestUrl = `${apiConfig.driveApi}/root${requestPath}`
+ // Whether path is root, which requires some special treatment
+ const isRoot = requestPath === ''
+
+ // Go for file raw download link and query with only temporary link parameter
+ if (raw) {
+ const { data } = await axios.get(requestUrl, {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ params: {
+ select: '@microsoft.graph.downloadUrl,folder,file',
+ },
+ })
+
+ if ('folder' in data) {
+ res.status(400).json({ error: "Folders doesn't have raw download urls." })
+ return
+ }
+ if ('file' in data) {
+ res.redirect(data['@microsoft.graph.downloadUrl'])
+ return
+ }
+ }
+
+ // Querying current path identity (file or folder) and follow up query childrens in folder
+ // console.log(accessToken)
+
+ const { data: identityData } = await axios.get(requestUrl, {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ params: {
+ select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
+ },
+ })
+
+ if ('folder' in identityData) {
+ const { data: folderData } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/children`, {
+ headers: { Authorization: `Bearer ${accessToken}` },
+ params: next
+ ? {
+ select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
+ top: siteConfig.maxItems,
+ $skipToken: next,
+ }
+ : {
+ select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file',
+ top: siteConfig.maxItems,
+ },
+ })
+
+ // Extract next page token from full @odata.nextLink
+ const nextPage = folderData['@odata.nextLink'] ? folderData['@odata.nextLink'].match(/&\$skiptoken=(.+)/i)[1] : null
+
+ // Return paging token if specified
+ if (nextPage) {
+ res.status(200).json({ folder: folderData, next: nextPage })
+ } else {
+ res.status(200).json({ folder: folderData })
+ }
+ return
+ }
+ res.status(200).json({ file: identityData })
return
}
diff --git a/pages/onedrive-vercel-index-oauth/step-1.tsx b/pages/onedrive-vercel-index-oauth/step-1.tsx
new file mode 100644
index 0000000..7dc95b0
--- /dev/null
+++ b/pages/onedrive-vercel-index-oauth/step-1.tsx
@@ -0,0 +1,129 @@
+import Head from 'next/head'
+import router from 'next/router'
+
+import siteConfig from '../../config/site.json'
+import apiConfig from '../../config/api.json'
+import Navbar from '../../components/Navbar'
+import Footer from '../../components/Footer'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+
+export default function OAuthStep1() {
+ return (
+
+
+
{`OAuth Step 1 - ${siteConfig.title}`}
+
+
+
+
+
+
+
+
Welcome to your new onedrive-vercel-index 🎉
+
+
+ Authorisation is required as no valid{' '}
+ access_token or{' '}
+ refresh_token is
+ present on this deployed instance.
+
+
+
Step 1: Preparations
+
+ Check the following configurations (especially client_id and{' '}
+ client_secret (obfuscated)) and see if they match the official
+ ones specified in the documentation of{' '}
+
+ onedrive-vercel-index
+ {' '}
+ before proceeding with authorising this application with your own Microsoft account.
+
+
+
+
+
+
+
+ CLIENT_ID
+
+
+ {apiConfig.clientId}
+
+
+
+
+ CLIENT_SECRET*
+
+
+ {apiConfig.obfuscatedClientSecret}
+
+
+
+
+ REDIRECT_URI
+
+
+ {apiConfig.redirectUri}
+
+
+
+
+ Auth API URL
+
+
+ {apiConfig.authApi}
+
+
+
+
+ Drive API URL
+
+
+ {apiConfig.driveApi}
+
+
+
+
+ API Scope
+
+
+ {apiConfig.scope}
+
+
+
+
+
+
+
+ If you see anything
+ missing or incorrect, you need to reconfigure /config/api.json{' '}
+ and redeploy this instance.
+
+
+ ️ If you are previewing
+ this locally, you would need to reauthorise yourself when you eventually deploy the site on Vercel.
+
+
+
+ {
+ router.push('/onedrive-vercel-index-oauth/step-2')
+ }}
+ >
+ Proceed to OAuth
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/pages/onedrive-vercel-index-oauth/step-2.tsx b/pages/onedrive-vercel-index-oauth/step-2.tsx
new file mode 100644
index 0000000..578f28d
--- /dev/null
+++ b/pages/onedrive-vercel-index-oauth/step-2.tsx
@@ -0,0 +1,103 @@
+import Head from 'next/head'
+import router from 'next/router'
+import { useState } from 'react'
+
+import siteConfig from '../../config/site.json'
+import Navbar from '../../components/Navbar'
+import Footer from '../../components/Footer'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { extractAuthCodeFromRedirected, generateAuthorisationUrl } from '../../utils/accessTokenHandler'
+
+export default function OAuthStep2() {
+ const [oAuthRedirectedUrl, setOAuthRedirectedUrl] = useState('')
+ const [authCode, setAuthCode] = useState('')
+ const oAuthUrl = generateAuthorisationUrl()
+
+ return (
+
+
+
{`OAuth Step 2 - ${siteConfig.title}`}
+
+
+
+
+
+
+
+
Welcome to your new onedrive-vercel-index 🎉
+
+
+ Authorisation is required as no valid{' '}
+ access_token or{' '}
+ refresh_token is
+ present on this deployed instance.
+
+
+
Step 2: Get authorisation code
+
The OAuth URL has been generated for you:
+
{
+ window.open(oAuthUrl)
+ }}
+ >
+
+
+
+
+ {oAuthUrl}
+
+
+
+ Click on the link to get the{' '}
+ authorisation code . Your browser will
+ open a new tab to Microsoft's account login page. Authenticate with your Microsoft account and copy
+ the redirected URL down below.
+
+
+
{
+ setOAuthRedirectedUrl(e.target.value)
+ setAuthCode(extractAuthCodeFromRedirected(e.target.value))
+ }}
+ />
+
+
The authorisation code extracted is:
+
+ {authCode || Waiting for code... }
+
+
+
+ {authCode
+ ? '✅ You can now proceed onto the next step: requesting your access token and refresh token.'
+ : '❌ No valid code extracted.'}
+
+
+
+ {
+ router.push({ pathname: '/onedrive-vercel-index-oauth/step-3', query: { authCode } })
+ }}
+ >
+ Get tokens
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
new file mode 100644
index 0000000..419645d
--- /dev/null
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -0,0 +1,148 @@
+import Head from 'next/head'
+import router from 'next/router'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+
+import siteConfig from '../../config/site.json'
+import Navbar from '../../components/Navbar'
+import Footer from '../../components/Footer'
+import { requestTokenWithAuthCode } from '../../utils/accessTokenHandler'
+
+export default function OAuthStep3({ accessToken, refreshToken, error, description, errorUri }) {
+ return (
+
+
+
{`OAuth Step 3 - ${siteConfig.title}`}
+
+
+
+
+
+
+
+
Welcome to your new onedrive-vercel-index 🎉
+
+
+ Authorisation is required as no valid{' '}
+ access_token or{' '}
+ refresh_token is
+ present on this deployed instance.
+
+
+
Step 3: Get access and refresh tokens
+ {error ? (
+
+
+
+ Whoops, looks like we got a problem: {error}.
+
+
+ {description}
+
+ {errorUri && (
+
+ Check out{' '}
+
+ Microsoft's official explanation
+ {' '}
+ on the error message.
+
+ )}
+
+ {
+ router.push('/onedrive-vercel-index-oauth/step-1')
+ }}
+ >
+ Restart
+
+
+
+ ) : (
+
+
Success, the API returned what we needed.
+
+ {accessToken && (
+
+ {' '}
+
+ Acquired access_token:{' '}
+ {`${accessToken.substring(0, 30)}...`}.
+
+
+ )}
+ {refreshToken && (
+
+ {' '}
+
+ Acquired refresh_token:{' '}
+ {`${refreshToken.substring(0, 50)}...`}.
+
+
+ )}
+
+
+
Your onedrive-vercel-index should be up and running. Go back home to find out.
+
+
+ {
+ router.push('/')
+ }}
+ >
+ Back home
+
+
+
+ )}
+
+
+
+
+
+
+ )
+}
+
+export async function getServerSideProps({ query }) {
+ const { authCode } = query
+
+ // Return if no auth code is present
+ if (!authCode) {
+ return {
+ props: {
+ error: 'No auth code present',
+ description: 'Where is the auth code??? Did you follow step 2 you silly donut?',
+ },
+ }
+ }
+
+ const response = await requestTokenWithAuthCode(authCode)
+
+ // If error response, return invalid
+ if ('error' in response) {
+ return {
+ props: {
+ error: response.error,
+ description: response.errorDescription,
+ errorUri: response.errorUri,
+ },
+ }
+ }
+
+ const { expiryTime, accessToken, refreshToken } = response
+ return {
+ props: {
+ error: null,
+ expiryTime,
+ accessToken,
+ refreshToken,
+ },
+ }
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 2123b73..c88d993 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -19,7 +19,9 @@ module.exports = {
indigo: colors.indigo,
purple: colors.purple,
pink: colors.pink,
- teal: colors.teal
+ teal: colors.teal,
+ cyan: colors.cyan,
+ orange: colors.orange,
},
extend: {
fontFamily: {
diff --git a/utils/accessTokenHandler.ts b/utils/accessTokenHandler.ts
new file mode 100644
index 0000000..5053f68
--- /dev/null
+++ b/utils/accessTokenHandler.ts
@@ -0,0 +1,85 @@
+import axios from 'axios'
+import CryptoJS from 'crypto-js'
+import Keyv from 'keyv'
+import KeyvFile from 'keyv-file'
+
+import apiConfig from '../config/api.json'
+
+const AES_SECRET_KEY = 'onedrive-vercel-index'
+
+export function obfuscateToken(token: string): string {
+ // Encrypt token with AES
+ const encrypted = CryptoJS.AES.encrypt(token, AES_SECRET_KEY)
+ return encrypted.toString()
+}
+
+export function revealObfuscatedToken(obfuscated: string): string {
+ // Decrypt SHA256 obfuscated token
+ const decrypted = CryptoJS.AES.decrypt(obfuscated, AES_SECRET_KEY)
+ return decrypted.toString(CryptoJS.enc.Utf8)
+}
+
+export function generateAuthorisationUrl(): string {
+ const { clientId, redirectUri, authApi } = apiConfig
+ const authUrl = authApi.replace('/token', '/authorize')
+
+ // Construct URL parameters for OAuth2
+ const params = new URLSearchParams()
+ params.append('client_id', clientId)
+ params.append('redirect_uri', redirectUri)
+ params.append('response_type', 'code')
+ params.append('scope', 'files.readwrite offline_access')
+ params.append('response_mode', 'query')
+
+ return `${authUrl}?${params.toString()}`
+}
+
+export function extractAuthCodeFromRedirected(url: string): string {
+ // Return empty string if the url is not the defined redirect uri
+ if (!url.startsWith(apiConfig.redirectUri)) {
+ return ''
+ }
+
+ // New URL search parameter
+ const params = new URLSearchParams(url.split('?')[1])
+ return params.get('code') || ''
+}
+
+export async function requestTokenWithAuthCode(
+ code: string
+): Promise<
+ | { expiryTime: string; accessToken: string; refreshToken: string }
+ | { error: string; errorDescription: string; errorUri: string }
+> {
+ const { clientId, redirectUri, authApi } = apiConfig
+ const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
+
+ // Construct URL parameters for OAuth2
+ const params = new URLSearchParams()
+ params.append('client_id', clientId)
+ params.append('redirect_uri', redirectUri)
+ params.append('client_secret', clientSecret)
+ params.append('code', code)
+ params.append('grant_type', 'authorization_code')
+
+ // Request access token
+ return axios
+ .post(authApi, params, {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ })
+ .then(resp => {
+ const { expires_in, access_token, refresh_token } = resp.data
+ return { expiryTime: expires_in, accessToken: access_token, refreshToken: refresh_token }
+ })
+ .catch(err => {
+ const { error, error_description, error_uri } = err.response.data
+ return { error, errorDescription: error_description, errorUri: error_uri }
+ })
+}
+
+export function storeTokens(accessToken: string, refreshToken: string) {
+ // We can safely leverage Vercel's /tmp directory, and persist the tokens with file-system based KV storage
+ const kv = new Keyv({ store: new KeyvFile(), namespace: 'onedrive-vercel-index' })
+}
From 45ef22f34af26cae754ab301083a74c8da65a3d8 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 12:17:00 +0800
Subject: [PATCH 02/14] store access token and refresh token in the file system
/tmp
---
pages/api/index.ts | 38 ++++++++++++--------
pages/onedrive-vercel-index-oauth/step-3.tsx | 8 ++++-
utils/accessTokenHandler.ts | 18 +++++-----
utils/odAuthTokenStore.ts | 32 +++++++++++++++++
4 files changed, 70 insertions(+), 26 deletions(-)
create mode 100644 utils/odAuthTokenStore.ts
diff --git a/pages/api/index.ts b/pages/api/index.ts
index 77609a5..ea263c5 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -2,13 +2,12 @@ import { posix as pathPosix } from 'path'
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
-import Keyv from 'keyv'
-import { KeyvFile } from 'keyv-file'
import apiConfig from '../../config/api.json'
import siteConfig from '../../config/site.json'
import { revealObfuscatedToken } from '../../utils/accessTokenHandler'
import { compareHashedToken } from '../../utils/protectedRouteHandler'
+import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore'
const basePath = pathPosix.resolve('/', apiConfig.base)
const encodePath = (path: string) => {
@@ -22,26 +21,27 @@ const encodePath = (path: string) => {
const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
-// Store access token in memory, cuz Vercel doesn't provide key-value storage natively
-let _access_token = ''
-let _refresh_token = ''
-const getAccessToken = async () => {
- if (_access_token) {
- console.log('Fetch access token from memory.')
- return _access_token
+async function getAccessToken(): Promise {
+ const { accessToken, refreshToken } = await getOdAuthTokens()
+
+ // Return in storage access token if it is still valid
+ if (typeof accessToken === 'string') {
+ console.log('Fetch access token from storage.')
+ return accessToken
}
- // Return if refresh_token is empty
- if (!_refresh_token) {
+ // Return empty string if no refresh token is stored, which requires the application to be re-authenticated
+ if (typeof refreshToken !== 'string') {
console.log('No refresh token, return empty access token.')
return ''
}
+ // Fetch new access token with in storage refresh token
const body = new URLSearchParams()
body.append('client_id', apiConfig.clientId)
body.append('redirect_uri', apiConfig.redirectUri)
body.append('client_secret', clientSecret)
- body.append('refresh_token', _refresh_token)
+ body.append('refresh_token', refreshToken)
body.append('grant_type', 'refresh_token')
const resp = await axios.post(apiConfig.authApi, body, {
@@ -50,10 +50,18 @@ const getAccessToken = async () => {
},
})
- if (resp.data.access_token) {
- _access_token = resp.data.access_token
- return _access_token
+ if ('access_token' in resp.data && 'refresh_token' in resp.data) {
+ const { expires_in, access_token, refresh_token } = resp.data
+ await storeOdAuthTokens({
+ accessToken: access_token,
+ accessTokenExpiry: parseInt(expires_in),
+ refreshToken: refresh_token,
+ })
+ console.log('Fetch new access token with stored refresh token.')
+ return access_token
}
+
+ return ''
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
index 419645d..482b194 100644
--- a/pages/onedrive-vercel-index-oauth/step-3.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -5,7 +5,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import siteConfig from '../../config/site.json'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'
+
import { requestTokenWithAuthCode } from '../../utils/accessTokenHandler'
+import { storeOdAuthTokens } from '../../utils/odAuthTokenStore'
export default function OAuthStep3({ accessToken, refreshToken, error, description, errorUri }) {
return (
@@ -118,7 +120,7 @@ export async function getServerSideProps({ query }) {
return {
props: {
error: 'No auth code present',
- description: 'Where is the auth code??? Did you follow step 2 you silly donut?',
+ description: 'Where is the auth code? Did you follow step 2 you silly donut?',
},
}
}
@@ -137,6 +139,10 @@ export async function getServerSideProps({ query }) {
}
const { expiryTime, accessToken, refreshToken } = response
+
+ // We can safely leverage Vercel's /tmp directory, and persist the tokens with file-system based KV storage
+ await storeOdAuthTokens({ accessToken, accessTokenExpiry: parseInt(expiryTime), refreshToken })
+
return {
props: {
error: null,
diff --git a/utils/accessTokenHandler.ts b/utils/accessTokenHandler.ts
index 5053f68..5a99fea 100644
--- a/utils/accessTokenHandler.ts
+++ b/utils/accessTokenHandler.ts
@@ -1,24 +1,22 @@
import axios from 'axios'
import CryptoJS from 'crypto-js'
-import Keyv from 'keyv'
-import KeyvFile from 'keyv-file'
import apiConfig from '../config/api.json'
+// Just a disguise to obfuscate the client secret, used along with the following two functions
const AES_SECRET_KEY = 'onedrive-vercel-index'
-
-export function obfuscateToken(token: string): string {
+function obfuscateToken(token: string): string {
// Encrypt token with AES
const encrypted = CryptoJS.AES.encrypt(token, AES_SECRET_KEY)
return encrypted.toString()
}
-
export function revealObfuscatedToken(obfuscated: string): string {
// Decrypt SHA256 obfuscated token
const decrypted = CryptoJS.AES.decrypt(obfuscated, AES_SECRET_KEY)
return decrypted.toString(CryptoJS.enc.Utf8)
}
+// Generate the Microsoft OAuth 2.0 authorization URL, used for requesting the authorisation code
export function generateAuthorisationUrl(): string {
const { clientId, redirectUri, authApi } = apiConfig
const authUrl = authApi.replace('/token', '/authorize')
@@ -34,6 +32,8 @@ export function generateAuthorisationUrl(): string {
return `${authUrl}?${params.toString()}`
}
+// The code returned from the Microsoft OAuth 2.0 authorization URL is a request URL with hostname
+// http://localhost and URL parameter code. This function extracts the code from the request URL
export function extractAuthCodeFromRedirected(url: string): string {
// Return empty string if the url is not the defined redirect uri
if (!url.startsWith(apiConfig.redirectUri)) {
@@ -45,6 +45,9 @@ export function extractAuthCodeFromRedirected(url: string): string {
return params.get('code') || ''
}
+// After a successful authorisation, the code returned from the Microsoft OAuth 2.0 authorization URL
+// will be used to request an access token. This function requests the access token with the authorisation code
+// and returns the access token and refresh token on success.
export async function requestTokenWithAuthCode(
code: string
): Promise<
@@ -78,8 +81,3 @@ export async function requestTokenWithAuthCode(
return { error, errorDescription: error_description, errorUri: error_uri }
})
}
-
-export function storeTokens(accessToken: string, refreshToken: string) {
- // We can safely leverage Vercel's /tmp directory, and persist the tokens with file-system based KV storage
- const kv = new Keyv({ store: new KeyvFile(), namespace: 'onedrive-vercel-index' })
-}
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
new file mode 100644
index 0000000..462d701
--- /dev/null
+++ b/utils/odAuthTokenStore.ts
@@ -0,0 +1,32 @@
+// This should be only used on the server side, where the tokens are stored with KV store using
+// a file system based storage. The tokens are stored in the file system as JSON at /tmp path.
+
+import Keyv from 'keyv'
+import { KeyvFile } from 'keyv-file'
+
+const kv = new Keyv({
+ store: new KeyvFile(),
+})
+
+export async function storeOdAuthTokens({
+ accessToken,
+ accessTokenExpiry,
+ refreshToken,
+}: {
+ accessToken: string
+ accessTokenExpiry: number
+ refreshToken: string
+}): Promise {
+ await kv.set('access_token', accessToken, accessTokenExpiry)
+ await kv.set('refresh_token', refreshToken)
+}
+
+export async function getOdAuthTokens(): Promise<{ accessToken: unknown; refreshToken: unknown }> {
+ const accessToken = await kv.get('access_token')
+ const refreshToken = await kv.get('refresh_token')
+
+ return {
+ accessToken,
+ refreshToken,
+ }
+}
From 689a647b803ffcc7e10364109b83f5f2ffca0c61 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 12:28:33 +0800
Subject: [PATCH 03/14] fixed file name for serverless persistent store
---
utils/odAuthTokenStore.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index 462d701..1a360d5 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -1,11 +1,14 @@
// This should be only used on the server side, where the tokens are stored with KV store using
// a file system based storage. The tokens are stored in the file system as JSON at /tmp path.
+import os from 'os'
import Keyv from 'keyv'
import { KeyvFile } from 'keyv-file'
const kv = new Keyv({
- store: new KeyvFile(),
+ store: new KeyvFile({
+ filename: `${os.tmpdir()}/od-auth-token.json`,
+ }),
})
export async function storeOdAuthTokens({
From 978c6d82f67de6f0f424a14e7ecf2db5143319ae Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 12:36:30 +0800
Subject: [PATCH 04/14] check kv store path on deployment
---
utils/odAuthTokenStore.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index 1a360d5..cbfade4 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -5,6 +5,8 @@ import os from 'os'
import Keyv from 'keyv'
import { KeyvFile } from 'keyv-file'
+console.log(`${os.tmpdir()}/od-auth-token.json`)
+
const kv = new Keyv({
store: new KeyvFile({
filename: `${os.tmpdir()}/od-auth-token.json`,
From 3dde6ac73caefc97c7b5ce59c099265ef7d431b8 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 13:19:38 +0800
Subject: [PATCH 05/14] store tokens in memory directly
---
utils/odAuthTokenStore.ts | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index cbfade4..3e626b3 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -3,15 +3,16 @@
import os from 'os'
import Keyv from 'keyv'
-import { KeyvFile } from 'keyv-file'
+// import { KeyvFile } from 'keyv-file'
console.log(`${os.tmpdir()}/od-auth-token.json`)
-const kv = new Keyv({
- store: new KeyvFile({
- filename: `${os.tmpdir()}/od-auth-token.json`,
- }),
-})
+// const kv = new Keyv({
+// store: new KeyvFile({
+// filename: `${os.tmpdir()}/od-auth-token.json`,
+// }),
+// })
+const kv = new Keyv({})
export async function storeOdAuthTokens({
accessToken,
From 72153cc8527a352fee97be1ce64703ee03735ca0 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 15:17:39 +0800
Subject: [PATCH 06/14] store access token and refresh token in serverless mem
---
config/api.json | 1 -
config/site.json | 1 +
pages/api/index.ts | 36 +++++++++---
pages/onedrive-vercel-index-oauth/step-1.tsx | 2 +-
pages/onedrive-vercel-index-oauth/step-2.tsx | 22 ++++++--
pages/onedrive-vercel-index-oauth/step-3.tsx | 17 ++++--
...{accessTokenHandler.ts => oAuthHandler.ts} | 5 +-
utils/odAuthTokenStore.ts | 56 +++++++++++--------
8 files changed, 95 insertions(+), 45 deletions(-)
rename utils/{accessTokenHandler.ts => oAuthHandler.ts} (93%)
diff --git a/config/api.json b/config/api.json
index 5414703..2594f63 100644
--- a/config/api.json
+++ b/config/api.json
@@ -2,7 +2,6 @@
"clientId": "d87bcc39-1750-4ca0-ad54-f8d0efbb2735",
"obfuscatedClientSecret": "U2FsdGVkX1830zo3/pFDqaBCVBb37iLw3WnBDWGF9GIB2f4apzv0roemp8Y+iIxI3Ih5ecyukqELQEGzZlYiWg==",
"redirectUri": "http://localhost",
- "base": "/Public",
"authApi": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"driveApi": "https://graph.microsoft.com/v1.0/me/drive",
"scope": "Files.Read.All Files.ReadWrite.All offline_access"
diff --git a/config/site.json b/config/site.json
index 9a5db54..19f5912 100644
--- a/config/site.json
+++ b/config/site.json
@@ -1,6 +1,7 @@
{
"icon": "/icons/128.png",
"title": "Spencer's OneDrive",
+ "baseDirectory": "/Public",
"maxItems": 100,
"googleFontSans": "Inter",
"googleFontMono": "Fira Mono",
diff --git a/pages/api/index.ts b/pages/api/index.ts
index ea263c5..5fb4226 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -5,11 +5,14 @@ import axios from 'axios'
import apiConfig from '../../config/api.json'
import siteConfig from '../../config/site.json'
-import { revealObfuscatedToken } from '../../utils/accessTokenHandler'
+import { revealObfuscatedToken } from '../../utils/oAuthHandler'
import { compareHashedToken } from '../../utils/protectedRouteHandler'
-import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore'
+import TokenStore from '../../utils/odAuthTokenStore'
+
+const basePath = pathPosix.resolve('/', siteConfig.baseDirectory)
+const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
+const tokenStore = new TokenStore()
-const basePath = pathPosix.resolve('/', apiConfig.base)
const encodePath = (path: string) => {
let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path))
if (encodedPath === '/' || encodedPath === '') {
@@ -19,10 +22,8 @@ const encodePath = (path: string) => {
return `:${encodeURIComponent(encodedPath)}`
}
-const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
-
async function getAccessToken(): Promise {
- const { accessToken, refreshToken } = await getOdAuthTokens()
+ const { accessToken, refreshToken } = await tokenStore.getOdAuthTokens()
// Return in storage access token if it is still valid
if (typeof accessToken === 'string') {
@@ -52,7 +53,7 @@ async function getAccessToken(): Promise {
if ('access_token' in resp.data && 'refresh_token' in resp.data) {
const { expires_in, access_token, refresh_token } = resp.data
- await storeOdAuthTokens({
+ await tokenStore.storeOdAuthTokens({
accessToken: access_token,
accessTokenExpiry: parseInt(expires_in),
refreshToken: refresh_token,
@@ -65,6 +66,27 @@ async function getAccessToken(): Promise {
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ // If method is POST, then the API is called by the client to store acquired tokens
+ if (req.method === 'POST') {
+ const { obfuscatedAccessToken, accessTokenExpiry, obfuscatedRefreshToken } = req.body
+ const accessToken = revealObfuscatedToken(obfuscatedAccessToken)
+ const refreshToken = revealObfuscatedToken(obfuscatedRefreshToken)
+
+ if (typeof accessToken !== 'string' || typeof refreshToken !== 'string') {
+ res.status(400).send('Invalid request body')
+ return
+ }
+
+ await tokenStore.storeOdAuthTokens({
+ accessToken,
+ accessTokenExpiry,
+ refreshToken,
+ })
+ res.status(200).send('OK')
+ return
+ }
+
+ // If method is GET, then the API is a normal request to the OneDrive API for files or folders
const { path = '/', raw = false, next = '' } = req.query
// Sometimes the path parameter is defaulted to '[...path]' which we need to handle
diff --git a/pages/onedrive-vercel-index-oauth/step-1.tsx b/pages/onedrive-vercel-index-oauth/step-1.tsx
index 7dc95b0..39b4ef0 100644
--- a/pages/onedrive-vercel-index-oauth/step-1.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-1.tsx
@@ -28,7 +28,7 @@ export default function OAuthStep1() {
present on this deployed instance.
- Step 1: Preparations
+ Step 1/3: Preparations
Check the following configurations (especially client_id and{' '}
client_secret (obfuscated)) and see if they match the official
diff --git a/pages/onedrive-vercel-index-oauth/step-2.tsx b/pages/onedrive-vercel-index-oauth/step-2.tsx
index 578f28d..a589b67 100644
--- a/pages/onedrive-vercel-index-oauth/step-2.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-2.tsx
@@ -1,16 +1,19 @@
import Head from 'next/head'
import router from 'next/router'
import { useState } from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import siteConfig from '../../config/site.json'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { extractAuthCodeFromRedirected, generateAuthorisationUrl } from '../../utils/accessTokenHandler'
+import { LoadingIcon } from '../../components/Loading'
+import { extractAuthCodeFromRedirected, generateAuthorisationUrl } from '../../utils/oAuthHandler'
export default function OAuthStep2() {
const [oAuthRedirectedUrl, setOAuthRedirectedUrl] = useState('')
const [authCode, setAuthCode] = useState('')
+ const [buttonLoading, setButtonLoading] = useState(false)
+
const oAuthUrl = generateAuthorisationUrl()
return (
@@ -33,10 +36,10 @@ export default function OAuthStep2() {
present on this deployed instance.
- Step 2: Get authorisation code
+ Step 2/3: Get authorisation code
The OAuth URL has been generated for you:
{
window.open(oAuthUrl)
}}
@@ -87,10 +90,19 @@ export default function OAuthStep2() {
className="text-white bg-gradient-to-br from-green-500 to-cyan-400 hover:bg-gradient-to-bl focus:ring-4 focus:ring-green-200 dark:focus:ring-green-800 font-medium rounded-lg text-sm px-4 py-2.5 text-center disabled:cursor-not-allowed disabled:grayscale"
disabled={authCode === ''}
onClick={() => {
+ setButtonLoading(true)
router.push({ pathname: '/onedrive-vercel-index-oauth/step-3', query: { authCode } })
}}
>
- Get tokens
+ {buttonLoading ? (
+ <>
+ Requesting tokens
+ >
+ ) : (
+ <>
+ Get tokens
+ >
+ )}
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
index 482b194..80f0817 100644
--- a/pages/onedrive-vercel-index-oauth/step-3.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -6,8 +6,8 @@ import siteConfig from '../../config/site.json'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'
-import { requestTokenWithAuthCode } from '../../utils/accessTokenHandler'
-import { storeOdAuthTokens } from '../../utils/odAuthTokenStore'
+import { obfuscateToken, requestTokenWithAuthCode } from '../../utils/oAuthHandler'
+import axios from 'axios'
export default function OAuthStep3({ accessToken, refreshToken, error, description, errorUri }) {
return (
@@ -30,7 +30,7 @@ export default function OAuthStep3({ accessToken, refreshToken, error, descripti
present on this deployed instance.
- Step 3: Get access and refresh tokens
+ Step 3/3: Get access and refresh tokens
{error ? (
@@ -140,8 +140,15 @@ export async function getServerSideProps({ query }) {
const { expiryTime, accessToken, refreshToken } = response
- // We can safely leverage Vercel's /tmp directory, and persist the tokens with file-system based KV storage
- await storeOdAuthTokens({ accessToken, accessTokenExpiry: parseInt(expiryTime), refreshToken })
+ // await tokenStore.storeOdAuthTokens({ accessToken, accessTokenExpiry: parseInt(expiryTime), refreshToken })
+
+ // We perform a POST request to the default API route to store the tokens inside the main route memory
+ // This is a bit of a hack, but it's the only way to get the tokens to the main route
+ await axios.post('/api', {
+ obfuscatedAccessToken: obfuscateToken(accessToken),
+ accessTokenExpiry: parseInt(expiryTime),
+ obfuscatedRefreshToken: obfuscateToken(refreshToken),
+ })
return {
props: {
diff --git a/utils/accessTokenHandler.ts b/utils/oAuthHandler.ts
similarity index 93%
rename from utils/accessTokenHandler.ts
rename to utils/oAuthHandler.ts
index 5a99fea..8b8202b 100644
--- a/utils/accessTokenHandler.ts
+++ b/utils/oAuthHandler.ts
@@ -3,9 +3,10 @@ import CryptoJS from 'crypto-js'
import apiConfig from '../config/api.json'
-// Just a disguise to obfuscate the client secret, used along with the following two functions
+// Just a disguise to obfuscate required tokens (including but not limited to client secret,
+// access tokens, and refresh tokens), used along with the following two functions
const AES_SECRET_KEY = 'onedrive-vercel-index'
-function obfuscateToken(token: string): string {
+export function obfuscateToken(token: string): string {
// Encrypt token with AES
const encrypted = CryptoJS.AES.encrypt(token, AES_SECRET_KEY)
return encrypted.toString()
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index 3e626b3..aaf2315 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -1,38 +1,46 @@
// This should be only used on the server side, where the tokens are stored with KV store using
// a file system based storage. The tokens are stored in the file system as JSON at /tmp path.
-import os from 'os'
+// import os from 'os'
import Keyv from 'keyv'
// import { KeyvFile } from 'keyv-file'
-console.log(`${os.tmpdir()}/od-auth-token.json`)
+// console.log(`${os.tmpdir()}/od-auth-token.json`)
-// const kv = new Keyv({
-// store: new KeyvFile({
-// filename: `${os.tmpdir()}/od-auth-token.json`,
-// }),
-// })
-const kv = new Keyv({})
+export default class TokenStore {
+ kv: Keyv
-export async function storeOdAuthTokens({
- accessToken,
- accessTokenExpiry,
- refreshToken,
-}: {
- accessToken: string
- accessTokenExpiry: number
- refreshToken: string
-}): Promise {
- await kv.set('access_token', accessToken, accessTokenExpiry)
- await kv.set('refresh_token', refreshToken)
-}
+ constructor() {
+ this.kv = new Keyv()
-export async function getOdAuthTokens(): Promise<{ accessToken: unknown; refreshToken: unknown }> {
- const accessToken = await kv.get('access_token')
- const refreshToken = await kv.get('refresh_token')
+ // this.kv = new Keyv({
+ // store: new KeyvFile({
+ // filename: `${os.tmpdir()}/od-auth-token.json`,
+ // }),
+ // })
+ console.log('TokenStore constructor')
+ }
- return {
+ async getOdAuthTokens(): Promise<{ accessToken: unknown; refreshToken: unknown }> {
+ const accessToken = await this.kv.get('access_token')
+ const refreshToken = await this.kv.get('refresh_token')
+
+ return {
+ accessToken,
+ refreshToken,
+ }
+ }
+
+ async storeOdAuthTokens({
accessToken,
+ accessTokenExpiry,
refreshToken,
+ }: {
+ accessToken: string
+ accessTokenExpiry: number
+ refreshToken: string
+ }): Promise {
+ await this.kv.set('access_token', accessToken, accessTokenExpiry)
+ await this.kv.set('refresh_token', refreshToken)
}
}
From 71ba7ff67c928b7a04fb6f6719220eeac1bfe924 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 15:55:38 +0800
Subject: [PATCH 07/14] post obfuscated as application/json
---
pages/api/index.ts | 1 +
pages/onedrive-vercel-index-oauth/step-3.tsx | 18 +++++++++++++-----
utils/odAuthTokenStore.ts | 5 ++---
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/pages/api/index.ts b/pages/api/index.ts
index 5fb4226..5242392 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -24,6 +24,7 @@ const encodePath = (path: string) => {
async function getAccessToken(): Promise {
const { accessToken, refreshToken } = await tokenStore.getOdAuthTokens()
+ console.log(accessToken, refreshToken)
// Return in storage access token if it is still valid
if (typeof accessToken === 'string') {
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
index 80f0817..ed04b4b 100644
--- a/pages/onedrive-vercel-index-oauth/step-3.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -144,11 +144,19 @@ export async function getServerSideProps({ query }) {
// We perform a POST request to the default API route to store the tokens inside the main route memory
// This is a bit of a hack, but it's the only way to get the tokens to the main route
- await axios.post('/api', {
- obfuscatedAccessToken: obfuscateToken(accessToken),
- accessTokenExpiry: parseInt(expiryTime),
- obfuscatedRefreshToken: obfuscateToken(refreshToken),
- })
+ await axios.post(
+ '/api',
+ {
+ obfuscatedAccessToken: obfuscateToken(accessToken),
+ accessTokenExpiry: parseInt(expiryTime),
+ obfuscatedRefreshToken: obfuscateToken(refreshToken),
+ },
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }
+ )
return {
props: {
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index aaf2315..6b53912 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -1,9 +1,9 @@
// This should be only used on the server side, where the tokens are stored with KV store using
// a file system based storage. The tokens are stored in the file system as JSON at /tmp path.
-// import os from 'os'
+import os from 'os'
import Keyv from 'keyv'
-// import { KeyvFile } from 'keyv-file'
+import { KeyvFile } from 'keyv-file'
// console.log(`${os.tmpdir()}/od-auth-token.json`)
@@ -12,7 +12,6 @@ export default class TokenStore {
constructor() {
this.kv = new Keyv()
-
// this.kv = new Keyv({
// store: new KeyvFile({
// filename: `${os.tmpdir()}/od-auth-token.json`,
From 2e6c652ea1f0923dcb43d715f90db9d31eaa868d Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 16:56:18 +0800
Subject: [PATCH 08/14] lets try storing as tmp file again
---
pages/api/index.ts | 22 ++++--
pages/onedrive-vercel-index-oauth/step-3.tsx | 76 +++++++++++++-------
utils/odAuthTokenStore.ts | 54 +++++---------
3 files changed, 85 insertions(+), 67 deletions(-)
diff --git a/pages/api/index.ts b/pages/api/index.ts
index 5242392..9ba9882 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -1,17 +1,26 @@
+import os from 'os'
import { posix as pathPosix } from 'path'
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
+import Keyv from 'keyv'
+import { KeyvFile } from 'keyv-file'
import apiConfig from '../../config/api.json'
import siteConfig from '../../config/site.json'
import { revealObfuscatedToken } from '../../utils/oAuthHandler'
import { compareHashedToken } from '../../utils/protectedRouteHandler'
-import TokenStore from '../../utils/odAuthTokenStore'
+import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore'
const basePath = pathPosix.resolve('/', siteConfig.baseDirectory)
const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
-const tokenStore = new TokenStore()
+
+const keyv = new Keyv({
+ store: new KeyvFile({
+ filename: `${os.tmpdir()}/od-auth-token.json`,
+ }),
+})
+console.log(`kv init - ${os.tmpdir()}/od-auth-token.json`)
const encodePath = (path: string) => {
let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path))
@@ -23,8 +32,7 @@ const encodePath = (path: string) => {
}
async function getAccessToken(): Promise {
- const { accessToken, refreshToken } = await tokenStore.getOdAuthTokens()
- console.log(accessToken, refreshToken)
+ const { accessToken, refreshToken } = await getOdAuthTokens(keyv)
// Return in storage access token if it is still valid
if (typeof accessToken === 'string') {
@@ -54,10 +62,11 @@ async function getAccessToken(): Promise {
if ('access_token' in resp.data && 'refresh_token' in resp.data) {
const { expires_in, access_token, refresh_token } = resp.data
- await tokenStore.storeOdAuthTokens({
+ await storeOdAuthTokens({
accessToken: access_token,
accessTokenExpiry: parseInt(expires_in),
refreshToken: refresh_token,
+ keyv,
})
console.log('Fetch new access token with stored refresh token.')
return access_token
@@ -78,10 +87,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return
}
- await tokenStore.storeOdAuthTokens({
+ await storeOdAuthTokens({
accessToken,
accessTokenExpiry,
refreshToken,
+ keyv,
})
res.status(200).send('OK')
return
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
index ed04b4b..f81be4a 100644
--- a/pages/onedrive-vercel-index-oauth/step-3.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -1,5 +1,7 @@
+import axios from 'axios'
import Head from 'next/head'
import router from 'next/router'
+import { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import siteConfig from '../../config/site.json'
@@ -7,9 +9,49 @@ import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'
import { obfuscateToken, requestTokenWithAuthCode } from '../../utils/oAuthHandler'
-import axios from 'axios'
+import { LoadingIcon } from '../../components/Loading'
+
+export default function OAuthStep3({ accessToken, expiryTime, refreshToken, error, description, errorUri }) {
+ const [buttonContent, setButtonContent] = useState(
+ <>
+ Store tokens
+ >
+ )
+
+ const sendAuthTokensToServer = async () => {
+ setButtonContent(
+ <>
+ Store tokens
+ >
+ )
+
+ await axios
+ .post(
+ '/api',
+ {
+ obfuscatedAccessToken: obfuscateToken(accessToken),
+ accessTokenExpiry: parseInt(expiryTime),
+ obfuscatedRefreshToken: obfuscateToken(refreshToken),
+ },
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }
+ )
+ .then(_ => {
+ console.log('Tokens sent to server')
+ setButtonContent(
+ <>
+ Stored! Going home...
+ >
+ )
+ setTimeout(() => {
+ router.push('/')
+ }, 2000)
+ })
+ }
-export default function OAuthStep3({ accessToken, refreshToken, error, description, errorUri }) {
return (
@@ -89,16 +131,16 @@ export default function OAuthStep3({ accessToken, refreshToken, error, descripti
)}
-
Your onedrive-vercel-index should be up and running. Go back home to find out.
+
+ Click the button below to save these tokens securely on Vercel before they expire.
+
{
- router.push('/')
- }}
+ className="text-white bg-gradient-to-br from-teal-400 via-teal-500 to-teal-600 hover:bg-gradient-to-bl focus:ring-4 focus:ring-teal-200 dark:focus:ring-teal-800 font-medium rounded-lg text-sm px-4 py-2.5 text-center disabled:cursor-not-allowed disabled:grayscale"
+ onClick={sendAuthTokensToServer}
>
- Back home
+ {buttonContent}
@@ -140,24 +182,6 @@ export async function getServerSideProps({ query }) {
const { expiryTime, accessToken, refreshToken } = response
- // await tokenStore.storeOdAuthTokens({ accessToken, accessTokenExpiry: parseInt(expiryTime), refreshToken })
-
- // We perform a POST request to the default API route to store the tokens inside the main route memory
- // This is a bit of a hack, but it's the only way to get the tokens to the main route
- await axios.post(
- '/api',
- {
- obfuscatedAccessToken: obfuscateToken(accessToken),
- accessTokenExpiry: parseInt(expiryTime),
- obfuscatedRefreshToken: obfuscateToken(refreshToken),
- },
- {
- headers: {
- 'Content-Type': 'application/json',
- },
- }
- )
-
return {
props: {
error: null,
diff --git a/utils/odAuthTokenStore.ts b/utils/odAuthTokenStore.ts
index 6b53912..23c71f8 100644
--- a/utils/odAuthTokenStore.ts
+++ b/utils/odAuthTokenStore.ts
@@ -1,45 +1,29 @@
// This should be only used on the server side, where the tokens are stored with KV store using
// a file system based storage. The tokens are stored in the file system as JSON at /tmp path.
-import os from 'os'
import Keyv from 'keyv'
-import { KeyvFile } from 'keyv-file'
-// console.log(`${os.tmpdir()}/od-auth-token.json`)
+export async function getOdAuthTokens(keyv: Keyv): Promise<{ accessToken: unknown; refreshToken: unknown }> {
+ const accessToken = await keyv.get('access_token')
+ const refreshToken = await keyv.get('refresh_token')
-export default class TokenStore {
- kv: Keyv
-
- constructor() {
- this.kv = new Keyv()
- // this.kv = new Keyv({
- // store: new KeyvFile({
- // filename: `${os.tmpdir()}/od-auth-token.json`,
- // }),
- // })
- console.log('TokenStore constructor')
- }
-
- async getOdAuthTokens(): Promise<{ accessToken: unknown; refreshToken: unknown }> {
- const accessToken = await this.kv.get('access_token')
- const refreshToken = await this.kv.get('refresh_token')
-
- return {
- accessToken,
- refreshToken,
- }
- }
-
- async storeOdAuthTokens({
+ return {
accessToken,
- accessTokenExpiry,
refreshToken,
- }: {
- accessToken: string
- accessTokenExpiry: number
- refreshToken: string
- }): Promise {
- await this.kv.set('access_token', accessToken, accessTokenExpiry)
- await this.kv.set('refresh_token', refreshToken)
}
}
+
+export async function storeOdAuthTokens({
+ accessToken,
+ accessTokenExpiry,
+ refreshToken,
+ keyv,
+}: {
+ accessToken: string
+ accessTokenExpiry: number
+ refreshToken: string
+ keyv: Keyv
+}): Promise {
+ await keyv.set('access_token', accessToken, accessTokenExpiry)
+ await keyv.set('refresh_token', refreshToken)
+}
From 49c2e57333471f235af35fff734ba88f317d49c9 Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 17:03:46 +0800
Subject: [PATCH 09/14] wait some more before redirecting home
---
pages/onedrive-vercel-index-oauth/step-3.tsx | 50 ++++++++++----------
1 file changed, 25 insertions(+), 25 deletions(-)
diff --git a/pages/onedrive-vercel-index-oauth/step-3.tsx b/pages/onedrive-vercel-index-oauth/step-3.tsx
index f81be4a..a65d91d 100644
--- a/pages/onedrive-vercel-index-oauth/step-3.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-3.tsx
@@ -25,31 +25,29 @@ export default function OAuthStep3({ accessToken, expiryTime, refreshToken, erro
>
)
- await axios
- .post(
- '/api',
- {
- obfuscatedAccessToken: obfuscateToken(accessToken),
- accessTokenExpiry: parseInt(expiryTime),
- obfuscatedRefreshToken: obfuscateToken(refreshToken),
+ await axios.post(
+ '/api',
+ {
+ obfuscatedAccessToken: obfuscateToken(accessToken),
+ accessTokenExpiry: parseInt(expiryTime),
+ obfuscatedRefreshToken: obfuscateToken(refreshToken),
+ },
+ {
+ headers: {
+ 'Content-Type': 'application/json',
},
- {
- headers: {
- 'Content-Type': 'application/json',
- },
- }
- )
- .then(_ => {
- console.log('Tokens sent to server')
- setButtonContent(
- <>
- Stored! Going home...
- >
- )
- setTimeout(() => {
- router.push('/')
- }, 2000)
- })
+ }
+ )
+
+ setButtonContent(
+ <>
+ Stored! Going home...
+ >
+ )
+
+ setTimeout(() => {
+ router.push('/')
+ }, 3000)
}
return (
@@ -132,7 +130,9 @@ export default function OAuthStep3({ accessToken, expiryTime, refreshToken, erro
- Click the button below to save these tokens securely on Vercel before they expire.
+ Click the button below to save these tokens securely on Vercel before they expire. These tokens may
+ take a few seconds to populate, if you go back home and still see the welcome page telling you to
+ re-authenticate, go back home and do a hard refresh.
From 115b550cb524cc5784be5857fc76ad8b3307eadd Mon Sep 17 00:00:00 2001
From: spencerwooo
Date: Fri, 31 Dec 2021 20:03:17 +0800
Subject: [PATCH 10/14] store tokens in redis with upstash
---
package-lock.json | 261 +++++++++++++------
package.json | 6 +-
pages/api/index.ts | 12 +-
pages/onedrive-vercel-index-oauth/step-1.tsx | 6 +-
pages/onedrive-vercel-index-oauth/step-2.tsx | 6 +-
pages/onedrive-vercel-index-oauth/step-3.tsx | 59 +++--
utils/odAuthTokenStore.ts | 19 +-
7 files changed, 247 insertions(+), 122 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 00b9f86..19d6d07 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,19 +14,19 @@
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@headlessui/react": "^1.4.0",
+ "@keyv/redis": "^2.2.1",
"axios": "^0.21.1",
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
"jszip": "^3.7.1",
"keyv": "^4.0.4",
- "keyv-file": "^0.2.0",
"next": "^12.0.7",
"nextjs-progressbar": "^0.0.13",
"preview-office-docs": "^1.0.2",
"prismjs": "^1.23.0",
- "react": "17.0.2",
+ "react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.3",
- "react-dom": "17.0.2",
+ "react-dom": "^17.0.2",
"react-hot-toast": "^2.0.0",
"react-markdown": "^6.0.2",
"react-player": "^2.9.0",
@@ -826,6 +826,14 @@
"react-dom": "^16 || ^17 || ^18"
}
},
+ "node_modules/@keyv/redis": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.2.1.tgz",
+ "integrity": "sha512-LktyRl8kqplElJVN4AJVwj5dsJc7ulza/2kqiksQzW4YHYucPhyvoFeLc7hKBmO1cdFHO12Q+3sjM+fL6WCb3g==",
+ "dependencies": {
+ "ioredis": "^4.28.2"
+ }
+ },
"node_modules/@napi-rs/triples": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz",
@@ -2005,6 +2013,14 @@
}
]
},
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
+ "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2338,6 +2354,14 @@
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
"dev": true
},
+ "node_modules/denque": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
+ "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -3285,16 +3309,6 @@
"url": "https://www.patreon.com/infusion"
}
},
- "node_modules/fs-extra": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
- "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -4073,6 +4087,31 @@
"node": ">= 0.4"
}
},
+ "node_modules/ioredis": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.2.tgz",
+ "integrity": "sha512-kQ+Iv7+c6HsDdPP2XUHaMv8DhnSeAeKEwMbaoqsXYbO+03dItXt7+5jGQDRyjdRUV2rFJbzg7P4Qt1iX2tqkOg==",
+ "dependencies": {
+ "cluster-key-slot": "^1.1.0",
+ "debug": "^4.3.1",
+ "denque": "^1.1.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isarguments": "^3.1.0",
+ "p-map": "^2.1.0",
+ "redis-commands": "1.7.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ioredis"
+ }
+ },
"node_modules/is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
@@ -4501,14 +4540,6 @@
"node": ">=6"
}
},
- "node_modules/jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
"node_modules/jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@@ -4579,16 +4610,6 @@
"json-buffer": "3.0.1"
}
},
- "node_modules/keyv-file": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz",
- "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==",
- "dependencies": {
- "debug": "^4.1.1",
- "fs-extra": "^4.0.1",
- "tslib": "^1.9.3"
- }
- },
"node_modules/language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
@@ -4700,6 +4721,21 @@
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
+ },
+ "node_modules/lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5712,6 +5748,14 @@
"node": ">=4"
}
},
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -6398,6 +6442,30 @@
"node": ">=8.10.0"
}
},
+ "node_modules/redis-commands": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
+ "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
+ },
+ "node_modules/redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
+ "dependencies": {
+ "redis-errors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@@ -6936,6 +7004,11 @@
"node": ">=8"
}
},
+ "node_modules/standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
+ },
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -7443,7 +7516,8 @@
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -7652,14 +7726,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
- "engines": {
- "node": ">= 4.0.0"
- }
- },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -8524,6 +8590,14 @@
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
"requires": {}
},
+ "@keyv/redis": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.2.1.tgz",
+ "integrity": "sha512-LktyRl8kqplElJVN4AJVwj5dsJc7ulza/2kqiksQzW4YHYucPhyvoFeLc7hKBmO1cdFHO12Q+3sjM+fL6WCb3g==",
+ "requires": {
+ "ioredis": "^4.28.2"
+ }
+ },
"@napi-rs/triples": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz",
@@ -9352,6 +9426,11 @@
"resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-3.2.0.tgz",
"integrity": "sha512-vooFaGFL6ulEP1liiaWFBmmfuPm3cY3y7T9eB83ZTnYc/oFeAKsq3NcDrOkBC8XaauEE8zHQwI7k0+JSYiVQSQ=="
},
+ "cluster-key-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
+ "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -9622,6 +9701,11 @@
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
"dev": true
},
+ "denque": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
+ "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
+ },
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -10377,16 +10461,6 @@
"integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==",
"dev": true
},
- "fs-extra": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
- "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
- "requires": {
- "graceful-fs": "^4.1.2",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- }
- },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -10947,6 +11021,24 @@
"side-channel": "^1.0.4"
}
},
+ "ioredis": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.2.tgz",
+ "integrity": "sha512-kQ+Iv7+c6HsDdPP2XUHaMv8DhnSeAeKEwMbaoqsXYbO+03dItXt7+5jGQDRyjdRUV2rFJbzg7P4Qt1iX2tqkOg==",
+ "requires": {
+ "cluster-key-slot": "^1.1.0",
+ "debug": "^4.3.1",
+ "denque": "^1.1.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isarguments": "^3.1.0",
+ "p-map": "^2.1.0",
+ "redis-commands": "1.7.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^2.1.0"
+ }
+ },
"is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
@@ -11224,14 +11316,6 @@
"minimist": "^1.2.5"
}
},
- "jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
- "requires": {
- "graceful-fs": "^4.1.6"
- }
- },
"jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@@ -11298,16 +11382,6 @@
"json-buffer": "3.0.1"
}
},
- "keyv-file": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz",
- "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==",
- "requires": {
- "debug": "^4.1.1",
- "fs-extra": "^4.0.1",
- "tslib": "^1.9.3"
- }
- },
"language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
@@ -11409,6 +11483,21 @@
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
+ "lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
+ },
+ "lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
+ },
+ "lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
+ },
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -12157,6 +12246,11 @@
}
}
},
+ "p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
+ },
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -12666,6 +12760,24 @@
"picomatch": "^2.2.1"
}
},
+ "redis-commands": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
+ "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
+ },
+ "redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
+ },
+ "redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
+ "requires": {
+ "redis-errors": "^1.0.0"
+ }
+ },
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@@ -13042,6 +13154,11 @@
}
}
},
+ "standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
+ },
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -13451,7 +13568,8 @@
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
},
"tsutils": {
"version": "3.21.0",
@@ -13593,11 +13711,6 @@
}
}
},
- "universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
- },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/package.json b/package.json
index a34a02c..36c0b5e 100644
--- a/package.json
+++ b/package.json
@@ -15,19 +15,19 @@
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@headlessui/react": "^1.4.0",
+ "@keyv/redis": "^2.2.1",
"axios": "^0.21.1",
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
"jszip": "^3.7.1",
"keyv": "^4.0.4",
- "keyv-file": "^0.2.0",
"next": "^12.0.7",
"nextjs-progressbar": "^0.0.13",
"preview-office-docs": "^1.0.2",
"prismjs": "^1.23.0",
- "react": "17.0.2",
+ "react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.3",
- "react-dom": "17.0.2",
+ "react-dom": "^17.0.2",
"react-hot-toast": "^2.0.0",
"react-markdown": "^6.0.2",
"react-player": "^2.9.0",
diff --git a/pages/api/index.ts b/pages/api/index.ts
index 9ba9882..970e8da 100644
--- a/pages/api/index.ts
+++ b/pages/api/index.ts
@@ -4,7 +4,6 @@ import { posix as pathPosix } from 'path'
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
import Keyv from 'keyv'
-import { KeyvFile } from 'keyv-file'
import apiConfig from '../../config/api.json'
import siteConfig from '../../config/site.json'
@@ -15,13 +14,6 @@ import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore
const basePath = pathPosix.resolve('/', siteConfig.baseDirectory)
const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
-const keyv = new Keyv({
- store: new KeyvFile({
- filename: `${os.tmpdir()}/od-auth-token.json`,
- }),
-})
-console.log(`kv init - ${os.tmpdir()}/od-auth-token.json`)
-
const encodePath = (path: string) => {
let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path))
if (encodedPath === '/' || encodedPath === '') {
@@ -32,7 +24,7 @@ const encodePath = (path: string) => {
}
async function getAccessToken(): Promise {
- const { accessToken, refreshToken } = await getOdAuthTokens(keyv)
+ const { accessToken, refreshToken } = await getOdAuthTokens()
// Return in storage access token if it is still valid
if (typeof accessToken === 'string') {
@@ -66,7 +58,6 @@ async function getAccessToken(): Promise {
accessToken: access_token,
accessTokenExpiry: parseInt(expires_in),
refreshToken: refresh_token,
- keyv,
})
console.log('Fetch new access token with stored refresh token.')
return access_token
@@ -91,7 +82,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
accessToken,
accessTokenExpiry,
refreshToken,
- keyv,
})
res.status(200).send('OK')
return
diff --git a/pages/onedrive-vercel-index-oauth/step-1.tsx b/pages/onedrive-vercel-index-oauth/step-1.tsx
index 39b4ef0..08a1f39 100644
--- a/pages/onedrive-vercel-index-oauth/step-1.tsx
+++ b/pages/onedrive-vercel-index-oauth/step-1.tsx
@@ -1,5 +1,5 @@
import Head from 'next/head'
-import router from 'next/router'
+import { useRouter } from 'next/router'
import siteConfig from '../../config/site.json'
import apiConfig from '../../config/api.json'
@@ -8,6 +8,9 @@ import Footer from '../../components/Footer'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export default function OAuthStep1() {
+ const redis_url = process.env.REDIS_URL
+ const router = useRouter()
+
return (