|
@@ -1,54 +1,65 @@
|
|
|
-// Reference: https://dev.onedrive.com/README.htm
|
|
|
|
|
-import { dumpQuery, noop } from '@/common';
|
|
|
|
|
|
|
+// References
|
|
|
|
|
+// - https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
|
|
|
+import { dumpQuery, getUniqId, loadQuery, noop } from '@/common';
|
|
|
import { FORM_URLENCODED, VM_HOME } from '@/common/consts';
|
|
import { FORM_URLENCODED, VM_HOME } from '@/common/consts';
|
|
|
import { AUTHORIZING, ERROR, UNAUTHORIZED } from '@/common/consts-sync';
|
|
import { AUTHORIZING, ERROR, UNAUTHORIZED } from '@/common/consts-sync';
|
|
|
import { objectGet } from '@/common/object';
|
|
import { objectGet } from '@/common/object';
|
|
|
import {
|
|
import {
|
|
|
- getURI, getItemFilename, BaseService, isScriptFile, register,
|
|
|
|
|
|
|
+ BaseService,
|
|
|
|
|
+ getCodeChallenge,
|
|
|
|
|
+ getCodeVerifier,
|
|
|
|
|
+ getItemFilename,
|
|
|
|
|
+ getURI,
|
|
|
|
|
+ isScriptFile,
|
|
|
openAuthPage,
|
|
openAuthPage,
|
|
|
|
|
+ register,
|
|
|
} from './base';
|
|
} from './base';
|
|
|
|
|
|
|
|
const config = {
|
|
const config = {
|
|
|
client_id: process.env.SYNC_ONEDRIVE_CLIENT_ID,
|
|
client_id: process.env.SYNC_ONEDRIVE_CLIENT_ID,
|
|
|
- client_secret: process.env.SYNC_ONEDRIVE_CLIENT_SECRET,
|
|
|
|
|
redirect_uri: VM_HOME + 'auth_onedrive.html',
|
|
redirect_uri: VM_HOME + 'auth_onedrive.html',
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const OneDrive = BaseService.extend({
|
|
const OneDrive = BaseService.extend({
|
|
|
name: 'onedrive',
|
|
name: 'onedrive',
|
|
|
displayName: 'OneDrive',
|
|
displayName: 'OneDrive',
|
|
|
- urlPrefix: 'https://api.onedrive.com/v1.0',
|
|
|
|
|
|
|
+ urlPrefix: 'https://graph.microsoft.com/v1.0',
|
|
|
refreshToken() {
|
|
refreshToken() {
|
|
|
const refreshToken = this.config.get('refresh_token');
|
|
const refreshToken = this.config.get('refresh_token');
|
|
|
return this.authorized({
|
|
return this.authorized({
|
|
|
- refresh_token: refreshToken,
|
|
|
|
|
grant_type: 'refresh_token',
|
|
grant_type: 'refresh_token',
|
|
|
- })
|
|
|
|
|
- .then(() => this.prepare());
|
|
|
|
|
|
|
+ refresh_token: refreshToken,
|
|
|
|
|
+ }).then(() => this.prepare());
|
|
|
},
|
|
},
|
|
|
user() {
|
|
user() {
|
|
|
- const requestUser = () => this.loadData({
|
|
|
|
|
- url: '/drive',
|
|
|
|
|
- responseType: 'json',
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const requestUser = () =>
|
|
|
|
|
+ this.loadData({
|
|
|
|
|
+ url: '/drive/special/approot',
|
|
|
|
|
+ responseType: 'json',
|
|
|
|
|
+ });
|
|
|
|
|
+ let unauthorized = false;
|
|
|
return requestUser()
|
|
return requestUser()
|
|
|
- .catch((res) => {
|
|
|
|
|
- if (res.status === 401) {
|
|
|
|
|
- return this.refreshToken().then(requestUser);
|
|
|
|
|
- }
|
|
|
|
|
- throw res;
|
|
|
|
|
- })
|
|
|
|
|
- .catch((res) => {
|
|
|
|
|
- if (res.status === 400 && objectGet(res, 'data.error') === 'invalid_grant') {
|
|
|
|
|
|
|
+ .catch((res) => {
|
|
|
|
|
+ if (!unauthorized && res.status === 401) {
|
|
|
|
|
+ unauthorized = true;
|
|
|
|
|
+ return this.refreshToken().then(requestUser);
|
|
|
|
|
+ }
|
|
|
|
|
+ throw res;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((res) => {
|
|
|
|
|
+ if (
|
|
|
|
|
+ res.status === 400 &&
|
|
|
|
|
+ objectGet(res, 'data.error') === 'invalid_grant'
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return Promise.reject({
|
|
|
|
|
+ type: UNAUTHORIZED,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
return Promise.reject({
|
|
return Promise.reject({
|
|
|
- type: UNAUTHORIZED,
|
|
|
|
|
|
|
+ type: ERROR,
|
|
|
|
|
+ data: res,
|
|
|
});
|
|
});
|
|
|
- }
|
|
|
|
|
- return Promise.reject({
|
|
|
|
|
- type: ERROR,
|
|
|
|
|
- data: res,
|
|
|
|
|
});
|
|
});
|
|
|
- });
|
|
|
|
|
},
|
|
},
|
|
|
handleMetaError(res) {
|
|
handleMetaError(res) {
|
|
|
if (res.status === 404) {
|
|
if (res.status === 404) {
|
|
@@ -64,19 +75,17 @@ const OneDrive = BaseService.extend({
|
|
|
return this.loadData({
|
|
return this.loadData({
|
|
|
url: '/drive/special/approot/children',
|
|
url: '/drive/special/approot/children',
|
|
|
responseType: 'json',
|
|
responseType: 'json',
|
|
|
- })
|
|
|
|
|
- .then(data => data.value.filter(item => item.file && isScriptFile(item.name)).map(normalize));
|
|
|
|
|
|
|
+ }).then((data) =>
|
|
|
|
|
+ data.value
|
|
|
|
|
+ .filter((item) => item.file && isScriptFile(item.name))
|
|
|
|
|
+ .map(normalize),
|
|
|
|
|
+ );
|
|
|
},
|
|
},
|
|
|
get(item) {
|
|
get(item) {
|
|
|
const name = getItemFilename(item);
|
|
const name = getItemFilename(item);
|
|
|
return this.loadData({
|
|
return this.loadData({
|
|
|
- url: `/drive/special/approot:/${encodeURIComponent(name)}`,
|
|
|
|
|
- responseType: 'json',
|
|
|
|
|
- })
|
|
|
|
|
- .then(data => this.loadData({
|
|
|
|
|
- url: data['@content.downloadUrl'],
|
|
|
|
|
- delay: false,
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ url: `/drive/special/approot:/${encodeURIComponent(name)}:/content`,
|
|
|
|
|
+ });
|
|
|
},
|
|
},
|
|
|
put(item, data) {
|
|
put(item, data) {
|
|
|
const name = getItemFilename(item);
|
|
const name = getItemFilename(item);
|
|
@@ -88,8 +97,7 @@ const OneDrive = BaseService.extend({
|
|
|
},
|
|
},
|
|
|
body: data,
|
|
body: data,
|
|
|
responseType: 'json',
|
|
responseType: 'json',
|
|
|
- })
|
|
|
|
|
- .then(normalize);
|
|
|
|
|
|
|
+ }).then(normalize);
|
|
|
},
|
|
},
|
|
|
remove(item) {
|
|
remove(item) {
|
|
|
// return 204
|
|
// return 204
|
|
@@ -97,26 +105,43 @@ const OneDrive = BaseService.extend({
|
|
|
return this.loadData({
|
|
return this.loadData({
|
|
|
method: 'DELETE',
|
|
method: 'DELETE',
|
|
|
url: `/drive/special/approot:/${encodeURIComponent(name)}`,
|
|
url: `/drive/special/approot:/${encodeURIComponent(name)}`,
|
|
|
- })
|
|
|
|
|
- .catch(noop);
|
|
|
|
|
|
|
+ }).catch(noop);
|
|
|
},
|
|
},
|
|
|
- authorize() {
|
|
|
|
|
|
|
+ async authorize() {
|
|
|
|
|
+ this.session = {
|
|
|
|
|
+ state: getUniqId(),
|
|
|
|
|
+ codeVerifier: getCodeVerifier(),
|
|
|
|
|
+ };
|
|
|
const params = {
|
|
const params = {
|
|
|
client_id: config.client_id,
|
|
client_id: config.client_id,
|
|
|
- scope: 'onedrive.appfolder wl.offline_access',
|
|
|
|
|
|
|
+ scope: 'openid profile Files.ReadWrite.AppFolder offline_access',
|
|
|
response_type: 'code',
|
|
response_type: 'code',
|
|
|
redirect_uri: config.redirect_uri,
|
|
redirect_uri: config.redirect_uri,
|
|
|
|
|
+ state: this.session.state,
|
|
|
|
|
+ ...(await getCodeChallenge(this.session.codeVerifier)),
|
|
|
};
|
|
};
|
|
|
- const url = `https://login.live.com/oauth20_authorize.srf?${dumpQuery(params)}`;
|
|
|
|
|
|
|
+ const url = `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${dumpQuery(
|
|
|
|
|
+ params,
|
|
|
|
|
+ )}`;
|
|
|
openAuthPage(url, config.redirect_uri);
|
|
openAuthPage(url, config.redirect_uri);
|
|
|
},
|
|
},
|
|
|
checkAuth(url) {
|
|
checkAuth(url) {
|
|
|
- const redirectUri = `${config.redirect_uri}?code=`;
|
|
|
|
|
|
|
+ const redirectUri = `${config.redirect_uri}?`;
|
|
|
|
|
+ if (!url.startsWith(redirectUri)) return;
|
|
|
|
|
+ const query = loadQuery(url.slice(redirectUri.length));
|
|
|
|
|
+ const { state, codeVerifier } = this.session || {};
|
|
|
|
|
+ this.session = null;
|
|
|
|
|
+ if (query.state !== state || !query.code) return;
|
|
|
if (url.startsWith(redirectUri)) {
|
|
if (url.startsWith(redirectUri)) {
|
|
|
this.authState.set(AUTHORIZING);
|
|
this.authState.set(AUTHORIZING);
|
|
|
- this.checkSync(this.authorized({
|
|
|
|
|
- code: url.slice(redirectUri.length),
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ this.checkSync(
|
|
|
|
|
+ this.authorized({
|
|
|
|
|
+ code: query.code,
|
|
|
|
|
+ code_verifier: codeVerifier,
|
|
|
|
|
+ grant_type: 'authorization_code',
|
|
|
|
|
+ redirect_uri: config.redirect_uri,
|
|
|
|
|
+ }),
|
|
|
|
|
+ );
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -131,20 +156,21 @@ const OneDrive = BaseService.extend({
|
|
|
authorized(params) {
|
|
authorized(params) {
|
|
|
return this.loadData({
|
|
return this.loadData({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- url: 'https://login.live.com/oauth20_token.srf',
|
|
|
|
|
|
|
+ url: 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token',
|
|
|
prefix: '',
|
|
prefix: '',
|
|
|
headers: {
|
|
headers: {
|
|
|
'Content-Type': FORM_URLENCODED,
|
|
'Content-Type': FORM_URLENCODED,
|
|
|
},
|
|
},
|
|
|
- body: dumpQuery(Object.assign({}, {
|
|
|
|
|
- client_id: config.client_id,
|
|
|
|
|
- client_secret: config.client_secret,
|
|
|
|
|
- redirect_uri: config.redirect_uri,
|
|
|
|
|
- grant_type: 'authorization_code',
|
|
|
|
|
- }, params)),
|
|
|
|
|
|
|
+ body: dumpQuery(
|
|
|
|
|
+ Object.assign(
|
|
|
|
|
+ {
|
|
|
|
|
+ client_id: config.client_id,
|
|
|
|
|
+ },
|
|
|
|
|
+ params,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
responseType: 'json',
|
|
responseType: 'json',
|
|
|
- })
|
|
|
|
|
- .then((data) => {
|
|
|
|
|
|
|
+ }).then((data) => {
|
|
|
if (data.access_token) {
|
|
if (data.access_token) {
|
|
|
this.config.set({
|
|
this.config.set({
|
|
|
uid: data.user_id,
|
|
uid: data.user_id,
|
|
@@ -157,7 +183,7 @@ const OneDrive = BaseService.extend({
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
-if (config.client_id && config.client_secret) register(OneDrive);
|
|
|
|
|
|
|
+if (config.client_id) register(OneDrive);
|
|
|
|
|
|
|
|
function normalize(item) {
|
|
function normalize(item) {
|
|
|
return {
|
|
return {
|