Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Notion Connector #191

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/notion/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
"homepage": "https://github.com/verida/server-template#readme",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.693.0",
"@jitl/passport-notion": "^2.0.0",
"@notionhq/client": "^2.2.15",
"@oauth-everything/passport-discord": "^1.0.2",
"@sapphire/snowflake": "^3.4.2",
"@superfaceai/passport-twitter-oauth2": "^1.2.3",
"@types/passport-strategy": "^0.2.38",
"@verida/account-node": "^4.3.0",
"@verida/client-ts": "^4.3.0",
"@verida/did-client": "^4.3.0",
Expand Down Expand Up @@ -76,6 +78,7 @@
"passport": "^0.5.2",
"passport-facebook": "^3.0.0",
"passport-google-oauth20": "^2.0.0",
"passport-strategy": "^1.0.0",
"pdf-parse": "^1.1.1",
"string-strip-html": "8.5.0",
"tdlib-native": "^3.1.0",
Expand Down
102 changes: 102 additions & 0 deletions src/providers/notion/NotionStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Request } from "express";
import https from "https";
import { URL } from "url";
import passport from "passport";

export default class NotionStrategy {
name = "notion";
private _clientID: string;
private _clientSecret: string;
private _callbackURL: string;
private _authorizationURL: string;
private _tokenURL: string;

constructor({ clientID, clientSecret, callbackURL }: { clientID: string; clientSecret: string; callbackURL: string }) {
if (!clientID || !clientSecret || !callbackURL) {
throw new TypeError("Missing required options for NotionStrategy");
}
this._clientID = clientID;
this._clientSecret = clientSecret;
this._callbackURL = callbackURL;
this._authorizationURL = "https://api.notion.com/v1/oauth/authorize";
this._tokenURL = "https://api.notion.com/v1/oauth/token";
}

async authenticate(req: Request, options: any) {
options = options || {};
if (req.query && req.query.code) {
try {
const oauthData = await this.getOAuthAccessToken(req.query.code as string);

if (oauthData.owner.type !== "user") {
throw new Error(`Notion API token not owned by user, instead: ${oauthData.owner.type}`);
}

return oauthData;
} catch (error) {
this.error(error as Error);
}
} else {
const authUrl = new URL(this._authorizationURL);
authUrl.searchParams.set("client_id", this._clientID);
authUrl.searchParams.set("redirect_uri", this._callbackURL);
authUrl.searchParams.set("response_type", "code");
this.redirect(authUrl.toString());
}
}

error(err: any) {
throw new Error("Error occurred in NotionStrategy");
}

fail(info: any) {
throw new Error("Failure in NotionStrategy");
}

success(user: any) {
throw new Error("Success callback not implemented.");
}

redirect(url: string) {
throw new Error("Redirect callback not implemented.");
}

private async getOAuthAccessToken(code: string): Promise<any> {
const accessTokenBody = {
grant_type: "authorization_code",
code,
redirect_uri: this._callbackURL,
};
const encodedCredential = Buffer.from(`${this._clientID}:${this._clientSecret}`).toString("base64");

const requestOptions = {
hostname: new URL(this._tokenURL).hostname,
path: new URL(this._tokenURL).pathname,
headers: {
Authorization: `Basic ${encodedCredential}`,
"Content-Type": "application/json",
},
method: "POST",
};

return new Promise((resolve, reject) => {
const accessTokenRequest = https.request(requestOptions, (res) => {
let data = "";
res.on("data", (d) => {
data += d;
});
res.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (error) {
reject(error);
}
});
});

accessTokenRequest.on("error", reject);
accessTokenRequest.write(JSON.stringify(accessTokenBody));
accessTokenRequest.end();
});
}
}
110 changes: 110 additions & 0 deletions src/providers/notion/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Request, Response } from "express";
import { Client } from "@notionhq/client";
import Base from "../BaseProvider";
import { NotionProviderConfig } from "./interfaces";
import { ConnectionCallbackResponse, PassportProfile } from "../../interfaces";
import passport from "passport";
import NotionStrategy from "./NotionStrategy"; // Import the corrected NotionStrategy class

export default class NotionProvider extends Base {
protected config: NotionProviderConfig;

public getProviderName() {
return "notion";
}

public getProviderLabel() {
return "Notion";
}

public getProviderApplicationUrl() {
return "https://www.notion.so/";
}

public syncHandlers(): any[] {
return [];
}

public getScopes(): string[] {
return ["read_content", "read_comment"];
}

public async connect(req: Request, res: Response, next: any): Promise<any> {
this.init();
const auth = passport.authenticate("notion", {
scope: this.getScopes().join(" "),
});
return auth(req, res, next);
}

public async callback(req: Request, res: Response, next: any): Promise<ConnectionCallbackResponse> {
this.init();
return new Promise((resolve, reject) => {
passport.authenticate(
"notion",
{ failureRedirect: "/failure/notion", failureMessage: true },
(err: any, user: any) => {

console.log("++++++")
console.log(user)
if (err) {
return reject(err);
}
if (!user) {
return reject(new Error("No user data returned from Notion"));
}

const profile = this.formatProfile(user.profile);

resolve({
id: profile.id,
accessToken: user.accessToken,
refreshToken: user.refreshToken,
profile: {
username: profile.connectionProfile.username,
...profile,
},
});
}
)(req, res, next);
});
}

public async getApi(accessToken?: string): Promise<Client> {
if (!accessToken) {
throw new Error("Access token is required");
}
return new Client({ auth: accessToken });
}

public init() {
passport.use(
new NotionStrategy({
clientID: this.config.clientId,
clientSecret: this.config.clientSecret,
callbackURL: this.config.callbackUrl,
})
);
}

private formatProfile(notionProfile: any): PassportProfile {
const email = notionProfile.email || null;

return {
id: notionProfile.id,
provider: this.getProviderName(),
displayName: notionProfile.name || email || notionProfile.id,
name: {
familyName: "",
givenName: notionProfile.name || "",
},
photos: [],
connectionProfile: {
username: email ? email.split("@")[0] : notionProfile.id,
readableId: email || notionProfile.id,
email: email,
verified: true,
},
};
}
}
11 changes: 11 additions & 0 deletions src/providers/notion/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BaseHandlerConfig, BaseProviderConfig } from "../../../src/interfaces";

export interface NotionProviderConfig extends BaseProviderConfig {
clientId: string;
clientSecret: string;
callbackUrl: string;
}

export interface NotionHandlerConfig extends BaseHandlerConfig {
batchSize: number
}
8 changes: 8 additions & 0 deletions src/serverconfig.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@
"messagesPerGroupLimit": 100,
"maxGroupSize": 50,
"useDbPos": true
},
"notion": {
"status": "active",
"label": "Notion",
"clientId": "",
"clientSecret": "",
"batchSize": 50,
"maxSyncLoops": 1
}
},
"providerDefaults": {
Expand Down
80 changes: 25 additions & 55 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -115,55 +115,6 @@
"@smithy/util-utf8" "^3.0.0"
tslib "^2.6.2"

"@aws-sdk/client-bedrock@^3.693.0":
version "3.693.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-bedrock/-/client-bedrock-3.693.0.tgz#dd10cd5b9d5e1f35107768c9ba71da7879fafd80"
integrity sha512-N/aSSdfeqCczWP9o4kSYwH0cARurO2S7TBUNe3RHx5Oe+3McLyrPvL2NJsCDdu4aPrhRvCV1K95X9I3z5bBw5A==
dependencies:
"@aws-crypto/sha256-browser" "5.2.0"
"@aws-crypto/sha256-js" "5.2.0"
"@aws-sdk/client-sso-oidc" "3.693.0"
"@aws-sdk/client-sts" "3.693.0"
"@aws-sdk/core" "3.693.0"
"@aws-sdk/credential-provider-node" "3.693.0"
"@aws-sdk/middleware-host-header" "3.693.0"
"@aws-sdk/middleware-logger" "3.693.0"
"@aws-sdk/middleware-recursion-detection" "3.693.0"
"@aws-sdk/middleware-user-agent" "3.693.0"
"@aws-sdk/region-config-resolver" "3.693.0"
"@aws-sdk/types" "3.692.0"
"@aws-sdk/util-endpoints" "3.693.0"
"@aws-sdk/util-user-agent-browser" "3.693.0"
"@aws-sdk/util-user-agent-node" "3.693.0"
"@smithy/config-resolver" "^3.0.11"
"@smithy/core" "^2.5.2"
"@smithy/fetch-http-handler" "^4.1.0"
"@smithy/hash-node" "^3.0.9"
"@smithy/invalid-dependency" "^3.0.9"
"@smithy/middleware-content-length" "^3.0.11"
"@smithy/middleware-endpoint" "^3.2.2"
"@smithy/middleware-retry" "^3.0.26"
"@smithy/middleware-serde" "^3.0.9"
"@smithy/middleware-stack" "^3.0.9"
"@smithy/node-config-provider" "^3.1.10"
"@smithy/node-http-handler" "^3.3.0"
"@smithy/protocol-http" "^4.1.6"
"@smithy/smithy-client" "^3.4.3"
"@smithy/types" "^3.7.0"
"@smithy/url-parser" "^3.0.9"
"@smithy/util-base64" "^3.0.0"
"@smithy/util-body-length-browser" "^3.0.0"
"@smithy/util-body-length-node" "^3.0.0"
"@smithy/util-defaults-mode-browser" "^3.0.26"
"@smithy/util-defaults-mode-node" "^3.0.26"
"@smithy/util-endpoints" "^2.1.5"
"@smithy/util-middleware" "^3.0.9"
"@smithy/util-retry" "^3.0.9"
"@smithy/util-utf8" "^3.0.0"
"@types/uuid" "^9.0.1"
tslib "^2.6.2"
uuid "^9.0.1"

"@aws-sdk/[email protected]":
version "3.693.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz#2fd7f93bd81839f5cd08c5e6e9a578b80572d3c4"
Expand Down Expand Up @@ -934,6 +885,14 @@
"@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0"

"@jitl/passport-notion@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@jitl/passport-notion/-/passport-notion-2.0.0.tgz#03b08c2ac77898ca913f1ce93ff4d68f739dccaf"
integrity sha512-vf0vCn44bdcY/F7CyA5p+6hdNxv9BvGlu44tQ/EZxPv/xwHQEGwKKy/Ls/QImmT1VBTZUBiXaDI9nowB3KdDsQ==
dependencies:
"@notionhq/client" "0.4.2"
passport-strategy "^1.0.0"

"@jsdevtools/ono@^7.1.3":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
Expand All @@ -951,6 +910,14 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==

"@notionhq/[email protected]":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@notionhq/client/-/client-0.4.2.tgz#7e6744cf7bfaa3f82d24cd72f47eadc2b8461173"
integrity sha512-OfU587WZ5YzH9f59BJb/+QY74B6rZEPyqZEn3vQB2dOu1EXtzyWDHkfZfakYXYogJ24mvBfS77KjnaCRrEzSKw==
dependencies:
"@types/node-fetch" "^2.5.10"
node-fetch "^2.6.1"

"@notionhq/client@^2.2.15":
version "2.2.15"
resolved "https://registry.yarnpkg.com/@notionhq/client/-/client-2.2.15.tgz#739fc8edb1357a2e2e35d026571fafe17c089606"
Expand Down Expand Up @@ -1835,6 +1802,14 @@
"@types/oauth" "*"
"@types/passport" "*"

"@types/passport-strategy@^0.2.38":
version "0.2.38"
resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.38.tgz#482abba0b165cd4553ec8b748f30b022bd6c04d3"
integrity sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==
dependencies:
"@types/express" "*"
"@types/passport" "*"

"@types/passport@*", "@types/[email protected]":
version "1.0.12"
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.12.tgz#7dc8ab96a5e895ec13688d9e3a96920a7f42e73e"
Expand Down Expand Up @@ -1897,11 +1872,6 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d"
integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==

"@types/uuid@^9.0.1":
version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==

"@types/ws@^8.5.10":
version "8.5.12"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
Expand Down Expand Up @@ -5750,7 +5720,7 @@ passport-oauth2@^1.5.0, passport-oauth2@^1.6.1:
uid2 "0.0.x"
utils-merge "1.x.x"

[email protected]:
[email protected], passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
Expand Down
Loading