Skip to content

Instantly share code, notes, and snippets.

@coffeeaddict
Created July 11, 2012 18:46
Show Gist options
  • Save coffeeaddict/3092309 to your computer and use it in GitHub Desktop.
Save coffeeaddict/3092309 to your computer and use it in GitHub Desktop.
Add a doorkeeper session token to the current user
class ApplicationControler < AC::Base
include FayeHelper
# ... existing code
before_filter {
return if current_user.nil?
if current_user.refresh_session_token
# make a new client with the new token
client = setup_faye_client
client.publish("/users/#{current_user.id}", { update_token: true });
end
}
end
var faye_client = new Faye.Client('http://<%= faye_host %>:9292/faye');
<% if current_user %>
var clientAuth = {
authToken: '<%= current_user.session_token.token %>',
authUser: <%= current_user.id %>,
outgoing: function(message, callback) {
// all meta messages except subscribe are allowed w/o auth
if ( message.channel.match(/^\/meta/) != null && message.channel != '/meta/subscribe' ) {
return callback(message);
}
if ( typeof(message.ext) != 'object' ) message.ext = {};
message.ext.authToken = clientAuth.authToken;
message.ext.authUser = clientAuth.authUser;
// console.log(message);
return callback(message);
}
};
faye_client.addExtension(clientAuth);
var sub = faye_client.subscribe("/users/<%= current_user.id %>", function (
if ( message.update_token ) {
// fetch a resource which will update clientAuth.updateToken
$.get('<%= user_session_token_path %>');
}
});
<% end %>
class ClientAuth
MASTER_TOKEN = 'something secret for ultimatly trusted clients';
def initialize(user)
@user = user
end
def outgoing(message, callback)
@client.debug 'client-auth: outgoing'
# Again, leave non-subscribe messages alone
if message['channel'] =~ /^\/meta\// && message['channel'] != '/meta/subscribe'
@client.debug 'client-auth is skipped for ?', message['channel']
return callback.call(message)
end
# Add ext field if it's not present
message['ext'] ||= {}
# Set the auth token
if @user
message['ext']['authUser'] = @user.id
message['ext']['authToken'] = @user.session_token.token
else
# just use the master token when no user is given
#
# add some security through obscurity to prevent the worst script kiddies
#
time = Time.now.to_i
message['ext']['authUser'] = time
message['ext']['authToken'] = MASTER_TOKEN + time.to_s(16)
end
# Carry on and send the message to the server
return callback.call(message)
end
def added(client)
client.debug 'client-auth was added'
@client = client
end
end
require './lib/faye_client_auth'
module FayeHelper
def faye_client
return setup_faye_client if @faye_client.nil?
@faye_client
end
def setup_faye_client
if !EM.reactor_running?
Thread.new { EM.run {} }
while !EM.reactor_running?
Rails.logger.debug "Waiting for a reactor"
sleep 1
end
end
# this is a great help when debugging, but it will clutter your logs.
#if !Rails.env.production?
# Faye::Logging.log_level = :debug
# Faye.logger = lambda { |m| Rails.logger.debug m }
#end
client = Faye::Client.new("http://0.0.0.0:9292/faye")
user = if defined? current_user
current_user
else
nil
end
client.add_extension(ClientAuth.new(user))
return ( @faye_client = client )
end
end
var faye = require('faye'),
redis = require('faye-redis'),
http = require('http'),
mysql = require('./mysql_connection.js');
var MASTER_TOKEN = 'something secret ...';
var server = http.createServer();
var bayeux = new faye.NodeAdapter({
mount: '/faye',
timeout: 25,
engine: {
type: redis,
host: 'localhost',
}
});
var serverAuth = {
cache: {},
incoming: function(message, callback) {
// Let all meta messages through
if (message.channel.match(/^\/meta/) != null && message.channel != "/meta/subscribe" ) {
return callback(message);
}
if (typeof(message.ext) != 'object') {
message.error = "invalid message";
return callback(message);
}
var authToken = message.ext.authToken,
authUser = message.ext.authUser,
logToken = message.ext.authToken.slice(0,9) + "...";
invalidate = function(msg) {
console.log(msg);
message.error = "Invalid token: " + authToken;
};
validate = function(expires_at) {
console.log(logToken + " is valid until " + new Date(expires_at));
serverAuth.cache[authToken] = expires_at;
};
if ( serverAuth.cache[authToken] !== undefined ) {
// check if not expired and let through
var now = new Date();
if (now.getTime() > serverAuth.cache[authToken]) {
invalidate(logToken + " has expired " + (now.getTime() - serverAuth.cache[authToken]) + " seconds ago");
}
return callback(message);
}
if ( authToken == (MASTER_TOKEN + authUser.toString(16)) ) {
console.log("master token received");
// someone handed a master token: must be a trusted client
return callback(message);
}
var sql = 'SELECT `oauth_access_tokens`.* FROM `oauth_access_tokens` ' +
' WHERE `oauth_access_tokens`.`resource_owner_id` = ?' +
' AND `oauth_access_tokens`.`token` = ?' +
' AND `oauth_access_tokens`.`application_id` = 0 LIMIT 1';
var database = mysql.connect();
database.query(sql, [authUser, authToken], function(err, rows) {
// there are no errors and at least 1 row
if ( err !== null ) return invalidate("errors occured in db.query: " + err);
if ( rows.length == 0 ) return invalidate("no results for " + authToken + "//" + authUser);
// the token has not been revoked
var access_token = rows[0];
if ( access_token.revoked_at !== null ) return invalidate(logToken + " was revoked");
// the token has not expired
var now = new Date();
var expires_at = ( access_token.created_at.getTime() - (now.getTimezoneOffset() * 60 * 1000) + (access_token.expires_in * 1000) ),
now_time = now.getTime();
if ( now_time > expires_at ) return invalidate(logToken + " has expired " + ((now_time - expires_at) / 1000) + " seconds ago");
validate(expires_at);
});
database.end();
return callback(message);destroy
},
};
bayeux.addExtension(serverAuth);
console.log("bayeux setup complete");
bayeux.listen(9292);
// npm install [email protected]
var mysql = require('mysql');
exports.connect = function() {
var database;
if ( process.env.RAILS_ENV == 'production' ) {
database = mysql.createConnection({
host : 'localhost',
user : 'production',
password : 'production',
database : 'production',
socketPath : '/var/run/mysqld/mysqld.sock',
});
} else if ( process.env.RAILS_ENV == 'test' ) {
database = mysql.createConnection({
host : 'localhost',
user : 'test',
password : 'test',
database : 'test',
socketPath : '/var/run/mysqld/mysqld.sock',
});
} else {
database = mysql.createConnection({
host : 'localhost',
user : 'root',
database : 'development',
socketPath : '/var/run/mysqld/mysqld.sock',
});
}
database.connect();
return database;
}
class User < AR::Base
# ... existing code
has_one :session_token,
:class_name => "Doorkeeper::AccessToken",
:foreign_key => :resource_owner_id,
:conditions => { application_id: 0 }
# @return [TrueClass,FalseClass] true if refreshed
def refresh_session_token
return false if !session_token.nil? && !session_token.expired?
# destroy the old token
session_token.destroy unless session_token.nil?
# create a new short-lived token
create_session_token( expires_in: 900 )
return true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment