kaizensoze / node-github

[DEPRECATED] Node.js wrapper for GitHub API
https://kaizensoze.github.io/node-github/
MIT License
29 stars 5 forks source link

Not Issue. Newbie Question on OAuth2 with GitHub #94

Closed lonniev closed 8 years ago

lonniev commented 8 years ago

If I have a GitHub client app that it a chatbot and I want that chatbot to be able to modify GitHub content on behalf of the user in a chat room (e.g. "bot create new github issue as @me").

Then I assume I need to have the chat room user be challenged for OAuth2 authentication when each user first asks the chatbot to a GitHub operation.

That is, the chatbot app doesn't need to authenticate with the GitHub API, it needs to get the current user's access token and pass that along in its request to the GitHub REST client.

Is this correct?

If so, how does my nodejs client code either force the OAuth login process or retrieve the access token for a user who has already OAuth authenticated to the GitHub API.

Now, because the IM service will be Gitter, is there already a Gitter API to get the current user's GitHub access token?

kaizensoze commented 8 years ago

Usually when you want to do a github action on behalf of a user you need to request permission via an app. If you go to your settings you can register a new application (https://github.com/settings/developers). You specify a callback url along with the desired permissions. Typically the user authorizes through the app with some sort of "Login with Github" button, but you could maybe have the bot post the direct authorize link that the user will click and then authorize (assuming they accept the requested permissions), triggering the callback url which will include the user's access token you'll need to store on the backend.

Here's a link to the doc: https://developer.github.com/v3/oauth/

kaizensoze commented 8 years ago

This sample nodejs github authorization code I had lying around might help:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <a href="/auth/github">Login</a>
</body>
</html>
"use strict";

var path = require('path');

var express = require('express');
var session = require('express-session');
var partials = require('express-partials');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');

var passport = require('passport');
var GitHubStrategy = require('passport-github2').Strategy;

var GitHubApi = require('github4');

var GITHUB_CLIENT_ID = 'XXX';
var GITHUB_CLIENT_SECRET = 'XXX';
var GITHUB_CLIENT_CALLBACK_URL = 'http://localhost:3000/auth/github/callback'

var GITHUB_CLIENT_AUTH_TOKEN = 'XXX';

function handleError(err) {
  console.error(err);
}

// github
var github = new GitHubApi({
  debug: true
});
github.authenticate({
  type: 'oauth',
  token: GITHUB_CLIENT_AUTH_TOKEN
});

// passport
passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

passport.use(new GitHubStrategy({
    clientID: GITHUB_CLIENT_ID,
    clientSecret: GITHUB_CLIENT_SECRET,
    callbackURL: GITHUB_CLIENT_CALLBACK_URL
  },
  addUserToBackend
));

function addUserToBackend(accessToken, refreshToken, profile, done) {
  ...
}

// express
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(partials());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(methodOverride());
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));

app.get('/auth/github',
  passport.authenticate('github', { scope: ['user:email'] }),
  function(req, res) {
});

app.get('/auth/github/callback',
  passport.authenticate('github', { failureRedirect: '/' }),
  function(req, res) {
    res.redirect('/');
});

app.listen(3000);
lonniev commented 8 years ago

I think you are saying that the bot would interact with the user as follows:

if the user already has asked the bot to do something for it in the recent past then there is context somewhere that has the access token. Find that and use it. if the user has not yet authorized the bot to act on its behalf, don’t do the requested operation but spit back the OAuth login URL to the chat room telling the user to click it if they want to delegate the bot. Somewhere save the resulting access token so that the check above succeeds.

--  Lonnie VanZandt 303-900-3048

On 29 February 2016 at 14:36:29, Joe Gallo (notifications@github.com) wrote:

Usually when you want to do a github action on behalf of a user you need to request permission via an app. If you go to your settings you can register a new application (https://github.com/settings/developers). You specify a callback url along with the desired permissions. Typically the user authorizes through the app with some sort of "Login with Github" button, but you could maybe have the bot post the direct authorize link that the user will click and then authorize the app (assuming they accept the requested permissions), triggering the callback url which will include the user's access token you'll need to store on the backend.

— Reply to this email directly or view it on GitHub.

lonniev commented 8 years ago

So I gather that addUserToBackend is the function that I would write which saves the user-specific access tokens for later use.

When I do a github.issue.createComment() or similar POST/PATCH operation that requires authentication, how then do I pass the access code? With an immediately prior github.authenticate() call or with some passport functor that automagically supplies the saved access token?

--  Lonnie VanZandt 303-900-3048

On 29 February 2016 at 15:00:41, Joe Gallo (notifications@github.com) wrote:

This sample nodejs github authorization code I had lying around might help:

<!DOCTYPE html>

Login

"use strict";

var path = require('path');

var express = require('express'); var session = require('express-session'); var partials = require('express-partials'); var bodyParser = require('body-parser'); var methodOverride = require('method-override');

var passport = require('passport'); var GitHubStrategy = require('passport-github2').Strategy;

var GitHubApi = require('github4');

var GITHUB_CLIENT_ID = 'XXX'; var GITHUB_CLIENT_SECRET = 'XXX'; var GITHUB_CLIENT_CALLBACK_URL = 'http://localhost:3000/auth/github/callback'

var GITHUB_CLIENT_AUTH_TOKEN = 'XXX';

function handleError(err) { console.error(err); }

// github var github = new GitHubApi({ debug: true }); github.authenticate({ type: 'oauth', token: GITHUB_CLIENT_AUTH_TOKEN });

// passport passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(obj, done) { done(null, obj); });

passport.use(new GitHubStrategy({ clientID: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET, callbackURL: GITHUB_CLIENT_CALLBACK_URL }, addUserToBackend ));

function addUserToBackend(accessToken, refreshToken, profile, done) { ... }

// express var app = express(); app.set('views', path.join(dirname, 'views')); app.set('view engine', 'ejs'); app.use(partials()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(methodOverride()); app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(path.join(dirname, 'public')));

app.get('/auth/github', passport.authenticate('github', { scope: ['user:email'] }), function(req, res) { });

app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/' }), function(req, res) { res.redirect('/'); });

app.listen(3000); — Reply to this email directly or view it on GitHub.

kaizensoze commented 8 years ago

Right, if you already have the user's access token, call github.authenticate({ type: "oauth", token: access_token }) before calling github.issue.createComment(). Otherwise, you'll need to request permission to create an issue for them and you'd add logic to store the user's info in addUserToBackend.

lonniev commented 8 years ago

Rough code using hardcoded Basic Auth for the open APIs (search, etc) is working. So, I know that the plumping is properly in place.

Now, if I don't want to be responsible for storing user's credentials or access tokens, would it be reasonable to delegate all that to something like stormpath?

The bot would query a stormpath dictionary for the chat user's credential. When not present, the bot would clue the user to OAuth authenticate with GitHub. When the callback came back to the chatbot app, it would persist the access token with the stormpath record for the user.

This all hasn't been done before? There's not some convenient npm installer to just do this?

kaizensoze commented 8 years ago

Now, if I don't want to be responsible for storing user's credentials or access tokens, would it be reasonable to delegate all that to something like stormpath?

How/where you persist the data is up to you, whether it's via Stormpath or some Parse alternative.

This all hasn't been done before? There's not some convenient npm installer to just do this?

Not that I know of.

kaizensoze commented 8 years ago

I'm going to close this since the remainder is beyond the scope of the node-github client.

lonniev commented 8 years ago

Yes, I agree. The github API is working fine, no issue with it. Thanks for the opportunity to use your repo as a forum to discuss this matter of how to integrate the github API into the larger use case.

lonniev commented 8 years ago

For posterity and peers: Parse apparently is scheduled to go offline in 2017. A net-recommended alternative is Firebase. (Parse's server is also available as an open-source nodejs module.)