Browse files

Reorganized things to move to a 3rd-party session approach

  • Loading branch information...
1 parent d037622 commit 44561eb42fb53fa78703e0051f17f7835fbff3d8 @ggoodman ggoodman committed May 14, 2012
View
1 lib/stores.coffee
@@ -3,6 +3,7 @@ Database = require("./stores/memory").Database
module.exports =
users: new Database(filename: "/tmp/users.json")
auths: new Database(filename: "/tmp/auths.json")
+ sessions: new Database(filename: "/tmp/sessions.json")
plunks: new Database
filename: "/tmp/plunks.json"
comparator: (item) -> Date.parse(item.updated_at or item.created_at)
View
5 lib/stores/memory.coffee
@@ -53,6 +53,7 @@ class module.exports.Database extends events.EventEmitter
get: (key, cb) ->
value = clone(@items[key])
+
@emit "get", key, value
cb(null, value) if cb
@@ -61,11 +62,13 @@ class module.exports.Database extends events.EventEmitter
set: (key, value, cb) ->
@del(key) # To make sure that the sorted index is property reflected
+ value = clone(value)
+
index =
if @options.comparator then _.sortedIndex(@items, value, @options.comparator)
else 0 # Always add items to the front unless the comparator suggests otherwise
- @items[key] = clone(value)
+ @items[key] = value
@keys.splice(index, 0, key)
@emit "set", key, value
View
3 servers/api/chains/plunks/create.coffee
@@ -63,5 +63,4 @@ creater.push (json, next) ->
generateUniqueId (err, id) ->
if err then cb(err)
- else context.plunks.set id, json, (err) ->
- next(err, id, _.clone(json))
+ else context.plunks.set id, json, (err) -> next(err, id, _.clone(json))
View
4 servers/api/chains/plunks/prepare.coffee
@@ -24,8 +24,8 @@ preparer.push (id, plunk, next) ->
file.raw_url = "#{plunk.raw_url}#{filename}" # raw_url already has trailing slash
# Check tokens
- unless (@token is plunk.token) or (@user and plunk.user == @user.id)
- delete plunk.token
+ unless (@session and @session.plunks[plunk.id] == plunk.token) or (@user and plunk.user == @user.id)
+ delete plunk.token
next(null, plunk)
View
151 servers/api/index.coffee
@@ -3,6 +3,7 @@ request = require("request")
mime = require("mime")
express = require("express")
url = require("url")
+querystring = require("querystring")
revalidator = require("revalidator")
_ = require("underscore")._
@@ -15,19 +16,19 @@ genid = (len = 16, prefix = "", keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij
prefix
-{users, auths, plunks} = require("../../lib/stores")
+{users, auths, plunks, sessions} = require("../../lib/stores")
app.configure ->
app.use require("./middleware/cors").middleware()
app.use express.cookieParser()
app.use require("./middleware/json").middleware()
- app.use require("./middleware/auth").middleware(auths: auths)
+ app.use require("./middleware/session").middleware(sessions: sessions)
app.use require("./middleware/user").middleware(users: users)
- app.use require("./middleware/token").middleware()
app.use app.router
app.use (err, req, res, next) ->
+ throw err
json = if err.toJSON? then err.toJSON() else
message: err.message or "Unknown error"
code: err.code or 500
@@ -37,68 +38,94 @@ app.configure ->
app.set "jsonp callback", true
-
-
###
-# Authentication shinanigans
+# RESTful sessions
###
-app.get "/auth", (req, res, next) ->
- if req.user then return res.json _.defaults req.auth,
- user: req.user
-
- res.json {}
-
-app.del "/auth", (req, res, next) ->
- auths.del req.auth.id, (err) ->
- return next(err) if err
- res.send(204)
-
-app.get "/auths/github", (req, res, next) ->
- return next(new require("./errors").MissingArgument("token")) unless req.query.token
-
- if req.user then return res.json _.defaults req.auth,
- user: req.user
-
- request.get "https://api.github.com/user?access_token=#{req.query.token}", (err, response, body) ->
- return next(new require("./errors").Error(err)) if err
-
- try
- body = JSON.parse(body)
- catch e
- return next(new require("./errors").InvalidJSON)
-
- return next(new require("./errors").Unauthorized(body)) if response.status >= 400
-
- # Create a new authorization
- createAuth = (err, user) ->
- return next(err) if err
-
- auth =
- id: "tok-#{genid()}"
- user_key: user_key
- service: "github"
- service_token: req.query.token
+createSession = (token, user, cb) ->
+ session =
+ id: genid(32)
+ plunks: {}
+ token: token
+
+ session.user = user.id if user
+
+ session.url = nconf.get("url:api") + "/sessions/#{session.id}"
+ session.upgrade_url = "#{session.url}/upgrade"
- auths.set auth.id, auth, (err) ->
- return next(err) if err
+ sessions.set session.id, session, (err) ->
+ cb(err, session)
- json = _.defaults auth,
- user: user
+# Convenience endpoint to get the current session or create a new one
+app.get "/session", (req, res, next) ->
+ if req.session then res.redirect(nconf.get("url:api") + "/sessions/#{req.session.id}?#{url.parse(req.url).query}")
+ else
+ createSession null, null, (err, session) ->
+ if err then next(err)
+ else res.redirect(nconf.get("url:api") + "/sessions/#{session.id}?#{url.parse(req.url).query}")
- res.json json, 201
- # Create user if not exists
- user_key = "github:#{body.id}"
+app.post "/sessions", (req, res, next) ->
+ createSession null, null, (err, session) ->
+ if err then next(err)
+ else res.json(session, 201)
- users.get user_key, (err, user) ->
- unless user
- user =
- id: user_key
- login: body.login
- gravatar_id: body.gravatar_id
- users.set user_key, user, createAuth
- else createAuth(null, user)
+app.get "/sessions/:id", (req, res, next) ->
+ sessions.get req.params.id, (err, session) ->
+ if err then next(err)
+ else unless session then next(new apiErrors.NotFound)
+ else
+ unless session.user then res.json(session)
+ else users.get session.user, (err, user) ->
+ if err then next(err)
+ else res.json(_.extend(session, user: user))
+
+app.del "/sessions/:id/upgrade", (req, res, next) ->
+ sessions.get req.params.id, (err, session) ->
+ if err then next(err)
+ else unless session and session.user then next(new apiErrors.NotFound)
+ else
+ delete session.user
+
+ sessions.set session.id, session, (err) ->
+ if err then next(err)
+ else res.json(session)
+
+app.post "/sessions/:id/upgrade", (req, res, next) ->
+ unless token = req.param("token") then next(new apiErrors.MissingArgument("token"))
+ else
+ sessid = req.param("id")
+
+ request.get "https://api.github.com/user?access_token=#{token}", (err, response, body) ->
+ return next(new apiErrors.Error(err)) if err
+ return next(new apiErrors.PermissionDenied) if response.status >= 400
+
+ try
+ body = JSON.parse(body)
+ catch e
+ return next(new apiErrors.ParseError)
+
+ # Create a new authorization
+ upgradeSession = (err, user) ->
+ if err then next(err)
+ else sessions.get sessid, (err, session) ->
+ if err then next(err)
+ else sessions.set req.param("id"), _.extend(session, {user: user.id, token: token}), (err) ->
+ if err then next(err)
+ else res.json(_.extend(session, user: user), 201)
+
+ # Create user if not exists
+ user_key = "github:#{body.id}"
+
+ users.get user_key, (err, user) ->
+ if err then next(err)
+ else unless user
+ user =
+ id: user_key
+ login: body.login
+ gravatar_id: body.gravatar_id
+ users.set user_key, user, upgradeSession
+ else upgradeSession(null, user)
@@ -122,7 +149,7 @@ app.get "/plunks", (req, res, next) ->
start = Math.max(0, parseInt(req.param("p", "1"), 10) - 1) * pp
end = start + pp
- preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, token: req.token
+ preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, session: req.session
iterator = ([id, plunk], next) -> preparer(id, plunk, next)
plunks.list start, end, (err, list) ->
@@ -133,8 +160,8 @@ app.get "/plunks", (req, res, next) ->
# Create plunk
app.post "/plunks", (req, res, next) ->
- creater = waterfall require("./chains/plunks/create"), user: req.user, plunks: plunks, token: req.token
- preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, token: req.token
+ creater = waterfall require("./chains/plunks/create"), user: req.user, plunks: plunks, session: req.session
+ preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, session: req.session
responder = waterfall [creater, preparer]
responder req.body, (err, plunk) ->
@@ -144,7 +171,7 @@ app.post "/plunks", (req, res, next) ->
# Read plunk
app.get "/plunks/:id", (req, res, next) ->
fetcher = waterfall require("./chains/plunks/fetch"), plunks: plunks
- preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, token: req.token
+ preparer = waterfall require("./chains/plunks/prepare"), user: req.user, users: users, session: req.session
responder = waterfall [fetcher, preparer]
responder req.body, (err, plunks) ->
@@ -156,7 +183,7 @@ app.del "/plunks/:id", (req, res, next) ->
plunks.get req.params.id, (err, plunk) ->
if err then next(err)
else unless plunk then next(new apiErrors.NotFound)
- else unless (req.token is plunk.token) or (req.user and plunk.user == req.user.id) then next(new apiErrors.PermissionDenied)
+ else unless (req.user and plunk.user == req.user.id) or (req.session and req.session.plunks[plunk.id] == plunk.token) then next(new apiErrors.PermissionDenied)
else plunks.del req.params.id, (err) ->
if err then next(err)
else res.send(204)
View
7 servers/api/middleware/auth.coffee → servers/api/middleware/session.coffee
@@ -1,10 +1,11 @@
module.exports.middleware = (config = {}) ->
(req, res, next) ->
- if req.query.auth? then token = req.query.auth
+ if req.query.sessid then token = req.query.sessid
else if auth = req.header("Authorization") then [header, token] = auth.match(/^token (\S+)$/i)
- if token then config.auths.get token, (err, auth) ->
+ if token then config.sessions.get token, (err, session) ->
return next(err) if err
- req.auth = auth
+
+ req.session = session
next()
else next()
View
5 servers/api/middleware/token.coffee
@@ -1,5 +0,0 @@
-_ = require("underscore")._
-
-module.exports.middleware = (config = {}) ->
- (req, res, next) ->
- next()
View
7 servers/api/middleware/tokens.coffee
@@ -0,0 +1,7 @@
+_ = require("underscore")._
+
+module.exports.middleware = (config = {}) ->
+ (req, res, next) ->
+ if req.session
+ if auth = req.header("Authorization") then [header, token] = auth.match(/^token (\S+)$/i)
+ next()
View
14 servers/api/middleware/user.coffee
@@ -1,12 +1,12 @@
module.exports.middleware = (config = {}) ->
(req, res, next) ->
- if req.auth then config.users.get req.auth.user_key, (err, user) ->
+ unless req.session then next()
+ else config.users.get req.session.user, (err, user) ->
return next(err) if err
+
unless user
- delete req.auth
+ delete req.session
delete req.user
- else
- req.user = user
- req.user.id = req.auth.user_key
- next()
- else next()
+ else req.user = user
+
+ next()
View
5 servers/www/assets/js/landing.coffee
@@ -6,6 +6,7 @@
#= require models/plunks
#= require models/user
+#= require models/session
#= require views/userpanel
#= require views/gallery
@@ -24,8 +25,8 @@ Handlebars.registerHelper "arrayJoinSpace", (array) ->
((plunker) ->
$ ->
- plunker.user = new plunker.User
- plunker.user.fetch()
+ #plunker.user = new plunker.User
+ #plunker.session = new plunker.Session
plunker.collections.plunks = new plunker.PlunkCollection
View
21 servers/www/assets/js/models/plunks.coffee
@@ -4,6 +4,10 @@
params = _.extend {}, options,
url: model.url()
cache: false
+ headers: options.headers or {}
+
+ if tokens = $.cookie("plnkr_tokens")
+ params.headers["X-Plunker-Tokens"] = "tokens #{tokens}"
switch method
when "create"
@@ -38,6 +42,9 @@
# Reset synced state and changes
self._changes = {}
self._synced = _.clone(self.attributes)
+
+ @on "change:token", @onChangeToken
+ @onChangeToken() if @get("token") and @id
# Handle simple changes
_.each ["description", "index", "expires"], (key) ->
@@ -46,16 +53,26 @@
# Handle changes to files
@on "change:files", (model, value, options) ->
- previous = model.previous("files")
+ previous = model.previous("files") or {}
changes = {}
- delete self.changes.files # Kill the old changes; the whole files hash changes
+ delete self.changes.files if self.changes # Kill the old changes; the whole files hash changes
for filename, file of previous
unless _.isEqual(file, value[filename])
changes[filename] = file
self._changes.files = changes unless _.isEmpty(changes)
+
+ onChangeToken: (model, value, options) =>
+ if @get("token") and @id
+ tokens = {}
+ try
+ tokens = JSON.parse($.cookie("plnk_tokens") || "{}")
+ catch err
+ tokens[@id] = @get("token")
+
+ $.cookie "plnk_tokens", JSON.stringify(tokens), expires: 14
class plunker.PlunkCollection extends Backbone.Collection
url: => @get("url") or plunker.router.url("api") + "/plunks"
View
78 servers/www/assets/js/models/session.coffee
@@ -0,0 +1,78 @@
+((plunker) ->
+
+
+ class GithubAuthProvider
+ constructor: ->
+ self = @
+
+ _.extend @, Backbone.Events
+
+ plunker.mediator.on "intent:auth", (service) -> if service is "github" then self.showLoginWindow()
+ plunker.mediator.on "event:auth", (json) -> self.trigger("authorized", json) if json.service is "github"
+ plunker.mediator.on "error:auth", (json) -> self.trigger("denied", json) if json.service is "github"
+
+ showLoginWindow: (width = 1000, height = 650) ->
+ screenHeight = screen.height
+ left = Math.round((screen.width / 2) - (width / 2))
+ top = 0
+ if (screenHeight > height)
+ top = Math.round((screenHeight / 2) - (height / 2))
+
+ login = window.open "/auth/github", "Sign in with Github", """
+ left=#{left},top=#{top},width=#{width},height=#{height},personalbar=0,toolbar=0,scrollbars=1,resizable=1
+ """
+
+ winCloseCheck = ->
+ return if login && !login.closed
+ clearInterval(winListener)
+
+ winListener = setInterval(winCloseCheck, 1000)
+
+ if login then login.focus()
+
+
+ class plunker.Session extends Backbone.Model
+ url: -> @get("url") or plunker.router.url("api") + "/sessions"
+ initialize: ->
+ self = @
+
+ @github = new GithubAuthProvider
+ @github.on "authorized", (json) -> self.upgrade("github", json)
+
+ plunker.mediator.on "intent:logout", ->
+ self.downgrade()
+
+ @on "change:id", (model, value, options) ->
+ if value then $.cookie "plnk_session", value, expires: 7
+ else $.cookie "plnk_session", null
+
+ fetch: ->
+ self = @
+ plunker.request "/session",
+ success: (json) -> self.start(json)
+ error: (err) -> plunker.mediator.trigger "error", err
+
+ start: (json) ->
+ @clear(silent: true).set(json)
+ plunker.user.clear().set(json.user)
+ @
+
+ upgrade: (service, json) ->
+ self = @
+ if @id then plunker.request
+ url: @get("upgrade_url")
+ type: "post"
+ data: { service: service, token: json.token }
+ dataType: "json"
+ success: (json) -> self.start(json)
+
+ downgrade: (service, json) ->
+ self = @
+ if @id then plunker.request
+ url: @get("upgrade_url")
+ type: "delete"
+ success: -> self.start(json)
+
+
+
+)(@plunker or @plunker = {})
View
80 servers/www/assets/js/models/user.coffee
@@ -1,86 +1,8 @@
((plunker) ->
-
- class Auth extends Backbone.Model
-
- class AuthCollection extends Backbone.Collection
- model: Auth
+
class plunker.User extends Backbone.Model
initialize: ->
- self = @
-
- @auths = new AuthCollection
-
- plunker.mediator.on "event:auth", (auth) ->
- plunker.request "/auths/github"
- data: { token: auth.token }
- success: self.onAuthSuccess
- error: self.onAuthError
-
- plunker.mediator.on "intent:logout", ->
- plunker.request "/auth"
- type: "DELETE"
- success: -> self.logout()
- error: -> plunker.mediator.trigger "error",
- title: "Error logging out"
- body: """
- <p>There was an error while attempting to log out. Please try again.</p>
- <p>If the problem persists, please <a href="https://github.com/filearts/plunker/issues/new">file a bug report</a>.</p>
- """
-
- # Try to login based on cookie
- # @fetch()
-
- onAuthSuccess: (json) =>
- return if _.isEmpty(json)
-
- @token = json.id
-
- $.cookie "plnk_auth", @token,
- expires: 7
-
- @auths.add
- id: json.service
- token: json.service_token
- user: json.user
-
- @login json.user
-
- onAuthError: (json) =>
- plunker.mediator.trigger "error:login", arguments...
-
- fetch: ->
- plunker.request "/auth",
- success: @onAuthSuccess
-
-
- login: (json) ->
- @clear(silent: true).set(json)
- plunker.mediator.trigger "event:login", @
-
- logout: ->
- @clear()
- $.cookie("plnk_auth", null)
- plunker.mediator.trigger "event:logout"
-
- showLoginWindow: (width = 1000, height = 650) ->
- screenHeight = screen.height
- left = Math.round((screen.width / 2) - (width / 2))
- top = 0
- if (screenHeight > height)
- top = Math.round((screenHeight / 2) - (height / 2))
-
- login = window.open "/auth/github", "Sign in with Github", """
- left=#{left},top=#{top},width=#{width},height=#{height},personalbar=0,toolbar=0,scrollbars=1,resizable=1
- """
-
- winCloseCheck = ->
- return if login && !login.closed
- clearInterval(winListener)
-
- winListener = setInterval(winCloseCheck, 1000)
-
- if login then login.focus()
)(@plunker or @plunker = {})
View
19 servers/www/assets/js/plunker.coffee
@@ -3,21 +3,34 @@
#= require ../vendor/underscore
#= require ../vendor/backbone
+#= require models/user
+#= require models/session
+
((plunker) ->
_.extend plunker,
mediator: _.extend {}, Backbone.Events
models: {}
collections: {}
views: {}
request: (path, options = {}) ->
- if _.isObject(path) and _.isEmpty(options) then options = path
+ if _.isObject(path) and _.isEmpty(options)
+ options = path
+ path = ""
+
+ if _.isObject(options.data) then options.data = JSON.stringify(options.data)
+
$.ajax _.defaults options,
url: plunker.router.url("api") + path
dataType: "json"
- xhrFields: { withCredentials: true }
+ #xhrFields: { withCredentials: true }
+ error: (xhr, status, text) -> plunker.mediator.trigger "error", arguments...
beforeSend: (xhr) ->
- xhr.setRequestHeader("Authorization", "token #{token}") if token = $.cookie("plnk_auth")
+ xhr.setRequestHeader("Authorization", "token #{token}") if token = $.cookie("plnk_session")
+ xhr.setRequestHeader("Content-Type", "application/json")
#xhr.withCredentials = true # No longer needed *sigh*
+
+ plunker.user = new plunker.User
+ plunker.session = new plunker.Session
# For debugging purposes
plunker.mediator.on "all", -> console.log "[med]", arguments...
View
1 servers/www/index.coffee
@@ -53,6 +53,7 @@ authom.on "error", (req, res, data) ->
app.get "/", (req, res) ->
+ res.local "sessid", req.cookies.plnk_session or ""
res.render "landing"
app.get "/:id", (req, res) ->
View
2 servers/www/views/auth/error.jade
@@ -8,6 +8,6 @@ html
body
script
if (window.opener) {
- window.opener.plunker.mediator.trigger("event:auth", !{JSON.stringify(auth)});
+ window.opener.plunker.mediator.trigger("event:auth", auth.service, !{JSON.stringify(auth)});
window.close();
}
View
1 servers/www/views/landing.jade
@@ -11,6 +11,7 @@ html
script(src="http://html5shim.googlecode.com/svn/trunk/html5.js")
block scripts
!= js("landing")
+ script(src="#{url.api}/session?callback=plunker.session.start&sessid=#{sessid}")
script
plunker.router.map(!{JSON.stringify(url)});

0 comments on commit 44561eb

Please sign in to comment.