commit f5a4f6a525a138373d6aee19e2a56dd86029017d Author: Paul Rodwell Date: Wed Mar 16 14:13:37 2016 +0000 initial commit - work in progress diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f0a036 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +*.log +/client/*.js +/client/*.map diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..2a23673 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +# +test +client/*.coffee diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..3756285 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,32 @@ +module.exports = function (grunt) { + grunt.loadNpmTasks('grunt-browserify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-git-authors'); + + grunt.initConfig({ + + browserify: { + plugin: { + src: ['client/security.coffee'], + dest: 'client/security.js', + options: { + transform: ['coffeeify'], + browserifyOptions: { + extentions: ".coffee" + } + } + } + }, + + watch: { + all: { + files: ['client/*.coffee'], + tasks: ['build'] + } + } + }); + + grunt.registerTask('build', ['browserify']); + grunt.registerTask('default', ['build']); + +}; diff --git a/ReadMe.me b/ReadMe.me new file mode 100644 index 0000000..35cab91 --- /dev/null +++ b/ReadMe.me @@ -0,0 +1,10 @@ +# Federated Wiki - Security Plug-in: Social + +_**This security plug-in is currently a work in progress**_ + +Some major issues... + +1. Twitter does not return an email address. +2. Github does not support using a dynamic callback URL - each wiki would need to be configured separately. +3. Google supports dynamic callback URL, but it must match one that expected. +4. Facebook... diff --git a/client/security.coffee b/client/security.coffee new file mode 100644 index 0000000..4390e29 --- /dev/null +++ b/client/security.coffee @@ -0,0 +1,54 @@ +### + * Federated Wiki : Social Security Plugin + * + * Licensed under the MIT license. + * https://github.com/fedwiki/wiki-security-social/blob/master/LICENSE.txt +### + +### +1. Display login button - if there is no authenticated user +2. Display logout button - if the user is authenticated + +3. When user authenticated, claim site if unclaimed - and repaint footer. + +### + +dialogCallback = (msg) -> + alert('Dialog callback: ' + msg) + +update_footer = (owner, authUser) -> + # we update the owner and the login state in the footer, and + # populate the security dialog + + if owner + $('footer > #site-owner').html("Site Owned by: #{owner}") + + $('footer > #security').empty() + + if authUser is true + $('footer > #security').append "" + else + $('footer > #security').append "" + + $('footer > #security') + .delegate '#show-security-dialog', 'click', (e) -> + e.preventDefault() + securityDialog = window.open("/auth/loginDialog", "_blank", "menubar=no, location=no, chrome=yes, centerscreen") + + securityDialog.window.focus() + + + + +setup = (user) -> + + if (!$("link[href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css']").length) + $('').appendTo("head") + if (!$("link[href='/security/style.css']").length) + $('').appendTo("head") + + + + update_footer owner, authUser + +window.plugins.security = {setup} diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..a426651 --- /dev/null +++ b/client/style.css @@ -0,0 +1,3 @@ +#show-security-dialog { + color: gold; +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..e4f36a8 --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +// **index.js** +// Simple file so that if you require this directory +// in node it instead requires ./lib/social.coffee + +module.exports = require('./server/social'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e9d8348 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "wiki-security-social", + "version": "0.0.1", + "description": "Security plugin for Federated Wiki, using passport social plugins", + "author": "Paul Rodwell (http://rodwell.me)", + "license": "MIT", + "dependencies": { + "coffee-script": "1.10", + "passport": "^0.3.2", + "passport-twitter": "*", + "qs": "6.1" + }, + "devDependencies": { + "coffeeify": "*", + "grunt": "0.4", + "grunt-browserify": "~4", + "grunt-contrib-watch": "0.6", + "grunt-git-authors": "~3" + } +} diff --git a/server/social.coffee b/server/social.coffee new file mode 100644 index 0000000..eab7cf1 --- /dev/null +++ b/server/social.coffee @@ -0,0 +1,223 @@ +### + * Federated Wiki : Security Plugin : Social + * + * Copyright Ward Cunningham and other contributors + * Licensed under the MIT license. + * https://github.com/fedwiki/wiki-security-social/blob/master/LICENSE.txt +### +# **Persona : security.coffee** +# Module for Persona (BrowserID) based security. +# +# This module is based on the previously built-in security. + +#### Requires #### +fs = require 'fs' +path = require 'path' + +https = require 'https' +qs = require 'qs' + +passport = require 'passport' + + +# Export a function that generates security handler +# when called with options object. +module.exports = exports = (log, loga, argv) -> + security = {} + +#### Private stuff #### + + owner = '' + User = {} + + admin = argv.admin + + statusDir = argv.statusDir + + idFile = argv.id + + ids = {} + + schemes = {} + + + #### Public stuff #### + + # Attempt to figure out if the wiki is claimed or not, + # if it is return the owner. + + security.retrieveOwner = (cb) -> + fs.exists idFile, (exists) -> + if exists + fs.readFile(idFile, (err, data) -> + if err then return cb err + owner += data + cb()) + else + owner = '' + cb() + + security.getOwner = getOwner = -> + if ~owner.indexOf '@' + ownerName = owner.substr(0, owner.indexOf('@')) + else + ownerName = owner + ownerName = ownerName.split('.').join(' ') + ownerName + + security.setOwner = setOwner = (id, cb) -> + fs.exists idfile, (exists) -> + if !exists + fs.writeFile(idFile, id, (err) -> + if err then return cb err + console.log "Claining site for #{id}" + owner = # IDEA: + cb()) + else + cb() + + security.getUser = (req) -> + if req.session.email + return req.session.email + else + return '' + + security.isAuthorized = (req) -> + if [req.session.email, ''].indexOf(owner) > -1 + return true + else + return false + + security.isAdmin = (req) -> + if !(req.session.email? or admin?) + return false + if req.session.email is admin + return true + else + return false + + security.login = (updateOwner) -> + + + + + + + + + + + + security.logout = () -> + (req, res) -> + console.log "Logout...." + + security.defineRoutes = (app, cors, updateOwner) -> + + passport.serializeUser = (user, req, done) -> + done(null, user) + + passport.deserializeUser = (obj, req, done) -> + done(null, obj) + + + ### + if argv.github_clientID? and argv.github_clientSecret? + github = {} + github['clientID'] = argv.github_clientID + github['clientSecret'] = argv.github_clientSecret + ids['github'] = github + + GithubStrategy = require('passport-github').Strategy + + passport.use(new GithubStrategy({ + clientID: ids['github'].clientID + clientSecret: ids['github'].clientSecret + # this is not going to work - callback must equal that specified won github + # when the application was setup - it can't be dynamic.... + callbackURL: 'http://localhost:3000/auth/github/callback' + }, (accessToken, refreshToken, profile, cb) -> + User.findOrCreate({githubID: profile.id}, (err, user) -> + return cb(err, user)))) + ### + + if argv.twitter_consumerKey? and argv.twitter_consumerSecret? + schemes['twitter'] = true + twitter = {} + twitter['consumerKey'] = argv.twitter_consumerKey + twitter['consumerSecret'] = argv.twitter_consumerSecret + ids['twitter'] = twitter + + TwitterStrategy = require('passport-twitter').Strategy + + passport.use(new TwitterStrategy({ + consumerKey: ids['twitter'].consumerKey + consumerSecret: ids['twitter'].consumerSecret + callbackURL: '/auth/twitter/callback' + }, (accessToken, refreshToken, profile, cb) -> + console.log "Profile: ", profile + cb(null, profile))) + + ### + if argv.google_clientID? and argv.google_clientSecret? + google = {} + google['clientID'] = argv.google_clientID + google['clientSecret'] = argv.google_clientSecret + ids['google'] = google + + GoogleStrategy = require('passport-google-oauth20').Strategy + + passport.use(new GoogleStrategy({ + clientID: ids['google'].clientID + clientSecret: ids['google'].clientSecret + callbackURL: 'http://localhost:3000/auth/google/callback' + }, (accessToken, refreshToken, profile, cb) -> + console.log "Profile: ", profile + cb(null, profile))) + ### + + app.use(passport.initialize()) + app.use(passport.session()) + + ### Github + app.get('/auth/github', passport.authenticate('github'), (req, res) -> ) + app.get('/auth/github/callback', + passport.authenticate('github', { failureRedirect: '/'}), (req, res) -> + # do what ever happens on login + ) + ### + + # Twitter + app.get('/auth/twitter', passport.authenticate('twitter'), (req, res) -> ) + app.get('/auth/twitter/callback', + passport.authenticate('twitter', {failureRedirect: '/'}), (req, res) -> + console.log 'twitter logged in!!!!' + res.send('twitter logged in')) + + + ### Google + app.get('/auth/google', passport.authenticate('google', { scope: [ + 'https://www.googleapis.com/auth/plus.profile.emails.read' + ]})) + app.get('/auth/google/callback', + passport.authenticate('google', {failureRedirect: '/'}), (req, res) -> + console.log 'google logged in!!!!' + res.redirect('/view/welcome-visitors')) + ### + + app.get '/auth/loginDialog', (req, res) -> + + info = { + title: if owner + "Wiki Site Owner Sign-on" + else + "Sign-on to claim Wiki site" + schemes: "" + + } + res.render(path.join(__dirname, '..', 'views', 'securityDialog.html'), info) + + + + + security diff --git a/views/securityDialog.html b/views/securityDialog.html new file mode 100644 index 0000000..7a8e288 --- /dev/null +++ b/views/securityDialog.html @@ -0,0 +1,19 @@ + + + + Federated Wiki: Sign-on + + + + + + +
+ + {{title}} +
+
+ {{{schemes}}} +
+ +