diff --git a/package.json b/package.json index 0109994..fe04976 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,12 @@ "author": "zhl", "license": "ISC", "dependencies": { + "@fastify/cookie": "^9.3.1", "@fastify/cors": "^8.1.0", "@fastify/formbody": "^7.3.0", "@fastify/helmet": "^10.0.1", "@fastify/jwt": "^6.3.2", + "@fastify/session": "^10.7.2", "@fastify/view": "^7.4.1", "@typegoose/typegoose": "9.12.1", "axios": "^1.1.3", @@ -34,6 +36,7 @@ "mongoose-findorcreate": "^3.0.0", "nanoid": "^3.1.23", "node-schedule": "^2.1.1", + "oauth": "^0.10.0", "tracer": "^1.1.6" }, "devDependencies": { diff --git a/src/api.server.ts b/src/api.server.ts index 241770b..5b29a98 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -29,6 +29,16 @@ export class ApiServer { console.log('version::' + process.version) } private registerPlugins() { + this.server.register(require('@fastify/cookie')) + this.server.register(require('@fastify/session'), { + cookieName: 'sessionId', + secret: process.env.COOKIE_SECRET, + cookie: { secure: false }, + expires: 900000, + }) + this.server.addHook('preHandler', (request, reply, next) => { + next() + }) this.server.register(require('@fastify/formbody')) this.server.register(zReqParserPlugin) this.server.register(helmet, { diff --git a/src/controllers/twitter.controller.ts b/src/controllers/twitter.controller.ts index 2097a4b..19b0865 100644 --- a/src/controllers/twitter.controller.ts +++ b/src/controllers/twitter.controller.ts @@ -1,8 +1,14 @@ +import { ZError } from 'common/ZError' import BaseController, { ROLE_ANON } from 'common/base.controller' import { role, router } from 'decorators/router' import logger from 'logger/logger' import { AuthRecord } from 'modules/AuthRecord' -import { exchangeTwitterCodeForToken, getTwitterUserInfo } from 'services/twitter.svr' +import { + exchangeTwitterCodeForToken, + getOAuthAccessTokenWith, + getOAuthRequestToken, + getTwitterUserInfo, +} from 'services/twitter.svr' import { parseOauthState } from 'utils/net.util' class TwitterController extends BaseController { @@ -51,4 +57,51 @@ class TwitterController extends BaseController { } return res.view('/templates/twitter_redirect.ejs') } + + @role(ROLE_ANON) + @router('get /twitter/oauth/:address') + async twitterOauth1(req, res) { + let method = 'authenticate' + let { address } = req.params + if (!address) { + throw new ZError(10, 'address needed') + } + address = address.toLowerCase() + // @ts-ignore + const { oauthRequestToken, oauthRequestTokenSecret } = await getOAuthRequestToken() + console.log(`/oauth/twitter} ->`, { oauthRequestToken, oauthRequestTokenSecret }) + req.session.oauthRequestToken = oauthRequestToken + req.session.oauthRequestTokenSecret = oauthRequestTokenSecret + req.session.address = address + const authorizationUrl = `https://api.twitter.com/oauth/${method}?oauth_token=${oauthRequestToken}` + console.log('redirecting user to ', authorizationUrl) + res.redirect(authorizationUrl) + } + // for twitter oauth v1.0 + @role(ROLE_ANON) + @router('get /twitter/callback') + async twitterOauth1Callback(req, res) { + const { address, oauthRequestToken, oauthRequestTokenSecret } = req.session + console.log('request.query', req.query) + const { oauth_verifier: oauthVerifier } = req.query + const { oauthAccessToken, oauthAccessTokenSecret, results } = await getOAuthAccessTokenWith({ + oauthRequestToken, + oauthRequestTokenSecret, + oauthVerifier, + }) + const { user_id: userId /*, screen_name */ } = results + console.log('userId:', userId) + const record = await AuthRecord.insertOrUpdate( + { address, platform: 4 }, + { address, platform: 4, $inc: { version: 1 } }, + ) + record.nickname = results.screen_name + record.username = results.screen_name + record.openId = results.user_id + record.accessToken = oauthAccessToken + record.refreshToken = oauthAccessTokenSecret + record.tokenType = 'oauth1' + await record.save() + return res.view('/templates/twitter_redirect.ejs') + } } diff --git a/src/services/twitter.svr.ts b/src/services/twitter.svr.ts index f77fcf1..25a1972 100644 --- a/src/services/twitter.svr.ts +++ b/src/services/twitter.svr.ts @@ -1,5 +1,7 @@ const consumerKey = process.env.TWITTER_CLIENT_ID const consumerSecret = process.env.TWITTER_CLIENT_SECRET +import { OAuth } from 'oauth' +import { promisify } from 'util' export async function exchangeTwitterCodeForToken(code: string, vcode: string) { const url = 'https://api.twitter.com/2/oauth2/token' @@ -63,3 +65,49 @@ export async function getTwitterUserInfo(accessToken: string) { const data = await response.json() return data } + +var oauthConsumer = new OAuth( + 'https://api.twitter.com/oauth/request_token', + 'https://api.twitter.com/oauth/access_token', + process.env.TWITTER_API_KEY, + process.env.TWITTER_API_SECRET, + '1.0A', + process.env.TWITTER_OAUTH1_CB, + 'HMAC-SHA1', +) + +export async function getOAuthAccessTokenWith({ + oauthRequestToken, + oauthRequestTokenSecret, + oauthVerifier, +}: any = {}): Promise { + return new Promise((resolve, reject) => { + oauthConsumer.getOAuthAccessToken( + oauthRequestToken, + oauthRequestTokenSecret, + oauthVerifier, + function (error, oauthAccessToken, oauthAccessTokenSecret, results) { + return error + ? reject(new Error('Error getting OAuth access token')) + : resolve({ oauthAccessToken, oauthAccessTokenSecret, results }) + }, + ) + }) +} +export async function getOAuthRequestToken() { + return new Promise((resolve, reject) => { + oauthConsumer.getOAuthRequestToken(function (error, oauthRequestToken, oauthRequestTokenSecret, results) { + return error + ? reject(new Error('Error getting OAuth request token')) + : resolve({ oauthRequestToken, oauthRequestTokenSecret, results }) + }) + }) +} + +export async function oauthGetUserById(userId, { oauthAccessToken, oauthAccessTokenSecret }: any = {}) { + return promisify(oauthConsumer.get.bind(oauthConsumer))( + `https://api.twitter.com/1.1/users/show.json?user_id=${userId}`, + oauthAccessToken, + oauthAccessTokenSecret, + ).then(body => JSON.parse(body)) +} diff --git a/yarn.lock b/yarn.lock index cb91242..19737b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -109,6 +109,14 @@ ajv-formats "^2.1.1" fast-uri "^2.0.0" +"@fastify/cookie@^9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@fastify/cookie/-/cookie-9.3.1.tgz#48b89a356a23860c666e2fe522a084cc5c943d33" + integrity sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg== + dependencies: + cookie-signature "^1.1.0" + fastify-plugin "^4.0.0" + "@fastify/cors@^8.1.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-8.3.0.tgz#f03d745731b770793a1a15344da7220ca0d19619" @@ -161,6 +169,14 @@ fastify-plugin "^4.0.0" steed "^1.1.3" +"@fastify/session@^10.7.2": + version "10.7.2" + resolved "https://registry.yarnpkg.com/@fastify/session/-/session-10.7.2.tgz#8c4441133257075fc7eb79ce31b9a9d594e32ea8" + integrity sha512-PezR26nY8FMmAs4cww5rpnVyRkPDv2WQcVBLEKrtLTQy8mjL+twUKv9TLo/SXZYHZOIETzmquWoLYlnY3dSb+w== + dependencies: + fastify-plugin "^4.0.0" + safe-stable-stringify "^2.3.1" + "@fastify/view@^7.4.1": version "7.4.1" resolved "https://registry.yarnpkg.com/@fastify/view/-/view-7.4.1.tgz#265daba48386a5d3f69dfc446af468d72e0a8757" @@ -727,6 +743,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +cookie-signature@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4" + integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw== + cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" @@ -1763,6 +1784,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +oauth@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" + integrity sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q== + obliterator@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816"