Permalink
Browse files

Initial app skeleton with github authentication working.

  • Loading branch information...
1 parent 5d123ed commit 1935a72b15c31b0ee2dd7f24d0800ed7e738d16a @njx njx committed Mar 27, 2013
Showing with 327 additions and 15 deletions.
  1. +2 −0 .gitattributes
  2. +3 −0 .gitignore
  3. +18 −0 README.md
  4. +97 −0 app.js
  5. +86 −0 lib/Routes.js
  6. +27 −15 package.json
  7. +75 −0 spec/Routes.spec.js
  8. +2 −0 views/authFailed.html
  9. +8 −0 views/index.html
  10. +9 −0 views/layout.html
View
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
View
@@ -0,0 +1,3 @@
+config/
+node_modules/
+npm-debug.log
View
@@ -1,3 +1,21 @@
## Brackets Extension Registry
A node.js-powered registry for Brackets extensions.
+
+## Setup
+
+1. `npm install`
+2. Create a "config" folder at the top level. (This will be ignored by git.)
+3. In the "config" folder, put your SSL cert or [create a self-signed cert][1].
+ The key should be in "certificate.key" and the cert should be in "certificate.cert".
+4. Register a GitHub API client app. The callback URL must match the hostname of your
+ server. For testing, you could enter "https://localhost:4040/auth/github/callback".
+4. Also in the "config" folder, create a config.json file that contains:
+ * sessionSecret - key to use for session hashing
+ * githubClientId - client id for registered GitHub app
+ * githubClientSecret - client secret for register GitHub app
+ * hostname - hostname of the server, defaults to localhost
+ * port - port to run on, defaults to 4040
+5. `npm start`
+
+ [1]: http://www.akadia.com/services/ssh_test_certificate.html
View
97 app.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*jslint vars: true, plusplus: true, nomen: true, node: true, indent: 4, maxerr: 50 */
+
+"use strict";
+
+// Required modules
+var express = require("express"),
+ path = require("path"),
+ fs = require("fs"),
+ http = require("http"),
+ https = require("https"),
+ passport = require("passport"),
+ GitHubStrategy = require("passport-github").Strategy,
+ Routes = require("./lib/Routes");
+
+// Load cert and secret configuration
+var key = fs.readFileSync(path.resolve(__dirname, "config/certificate.key")),
+ cert = fs.readFileSync(path.resolve(__dirname, "config/certificate.cert")),
+ config = JSON.parse(fs.readFileSync(path.resolve(__dirname, "config/config.json")));
+
+config.hostname = config.hostname || "localhost";
+config.port = config.port || 4040;
+
+// Set up Passport for authentication
+
+// Session serialization. Since we don't need anything other than the registry user id
+// (which is of the form "authservice:id"), we just pass the user id into and out
+// of the session directly.
+passport.serializeUser(function (registryUserId, done) {
+ done(null, registryUserId);
+});
+passport.deserializeUser(function (registryUserId, done) {
+ done(null, registryUserId);
+});
+
+// Set up the GitHub authentication strategy. The registry user id
+// is just "github:" plus the user's GitHub id. (We use this instead of
+// the username since usernames can change.)
+// *** TODO: is that right?
+passport.use(
+ new GitHubStrategy(
+ {
+ clientID: config.githubClientId,
+ clientSecret: config.githubClientSecret,
+ callbackURL: "https://" + config.hostname + ":" + config.port + "/auth/github/callback" // *** TODO: real callback URL
+ },
+ function (accessToken, refreshToken, profile, done) {
+ done(null, "github:" + profile.id);
+ }
+ )
+);
+
+// Create and configure the app
+var app = express();
+app.configure(function () {
+ app.set("views", path.resolve(__dirname, "views"));
+ app.set("view engine", "html");
+ app.engine("html", require("hbs").__express);
+ app.use(express.logger("dev"));
+ app.use(express.cookieParser());
+ app.use(express.bodyParser());
+ app.use(express.methodOverride());
+ app.use(express.session({ secret: config.sessionSecret }));
+ app.use(passport.initialize());
+ app.use(passport.session());
+ app.use(app.router);
+ // JSLint doesn't like "express.static" because static is a keyword.
+ app.use(express["static"](path.resolve(__dirname, "public")));
+});
+
+// Set up routes
+Routes.setup(app);
+
+// Start the HTTPS server
+https.createServer({key: key, cert: cert}, app).listen(config.port);
View
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*jslint vars: true, plusplus: true, nomen: true, node: true, indent: 4, maxerr: 50 */
+
+"use strict";
+
+var passport = require("passport");
+
+function _index(req, res) {
+ res.render("index", {user: req.user});
+}
+
+function _authCallback(req, res) {
+ res.redirect("/");
+}
+
+function _authFailed(req, res) {
+ res.render("authFailed");
+}
+
+function _logout(req, res) {
+ req.logout();
+ res.redirect("/");
+}
+
+function _upload(req, res) {
+ // *** TODO: validate and then upload
+}
+
+function setup(app) {
+ app.get("/", _index);
+
+ app.get(
+ "/auth/github",
+ passport.authenticate("github"),
+ function (req, res) {
+ // The request will be redirected to GitHub for authentication, so this
+ // function will not be called.
+ }
+ );
+
+ app.get(
+ "/auth/github/callback",
+ // TODO: show error in-place on failure
+ passport.authenticate("github", { failureRedirect: "/auth/failed" }),
+ _authCallback
+ );
+
+ app.get(
+ "/auth/failed",
+ _authFailed
+ );
+
+ app.get("/logout", _logout);
+
+ app.post("/upload", _upload);
+}
+
+exports.setup = setup;
+
+// For unit testing only
+exports._index = _index;
+exports._authCallback = _authCallback;
+exports._authFailed = _authFailed;
+exports._logout = _logout;
View
@@ -1,16 +1,28 @@
{
- "name": "brackets-registry",
- "description": "node.js-powered server for registering Brackets extensions.",
- "version": "0.23.0",
- "homepage": "http://brackets.io",
- "license": "MIT",
- "issues": {
- "url": "http://github.com/adobe/brackets-registry/issues"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/adobe/brackets-registry.git",
- "branch": "",
- "SHA": ""
- }
-}
+ "name": "brackets-registry",
+ "description": "node.js-powered server for registering Brackets extensions.",
+ "version": "0.23.0",
+ "homepage": "http://brackets.io",
+ "license": "MIT",
+ "scripts": {
+ "start": "node app"
+ },
+ "issues": {
+ "url": "http://github.com/adobe/brackets-registry/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/adobe/brackets-registry.git",
+ "branch": "",
+ "SHA": ""
+ },
+ "dependencies": {
+ "express": "~3.1.0",
+ "hbs": "~2.1.0",
+ "passport": "~0.1.16",
+ "passport-github": "~0.1.3"
+ },
+ "devDependencies": {
+ "jasmine-node": "~1.4.0"
+ }
+}
View
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*jslint vars: true, plusplus: true, nomen: true, node: true, indent: 4, maxerr: 50 */
+/*global expect, describe, it, beforeEach, jasmine */
+
+"use strict";
+
+var Routes = require("../lib/Routes");
+
+describe("Routes", function () {
+ var req, res;
+
+ beforeEach(function () {
+ req = {};
+ res = {};
+ });
+
+ it("should redirect to home page on successful authentication", function () {
+ res.redirect = jasmine.createSpy();
+ Routes._authCallback(req, res);
+ expect(res.redirect).toHaveBeenCalledWith("/");
+ });
+
+ it("should render and inject correct data into the home page when user is not authenticated", function () {
+ res.render = jasmine.createSpy();
+ Routes._index(req, res);
+ expect(res.render).toHaveBeenCalled();
+ expect(res.render.mostRecentCall.args[0]).toBe("index");
+ expect(res.render.mostRecentCall.args[1].user).toBeUndefined();
+ });
+
+ it("should render and inject correct data into the home page when user is authenticated", function () {
+ req.user = "github:someuser";
+ res.render = jasmine.createSpy();
+ Routes._index(req, res);
+ expect(res.render).toHaveBeenCalled();
+ expect(res.render.mostRecentCall.args[0]).toBe("index");
+ expect(res.render.mostRecentCall.args[1].user).toBe("github:someuser");
+ });
+
+ it("should logout and redirect to home page when logging out", function () {
+ req.logout = jasmine.createSpy();
+ res.redirect = jasmine.createSpy();
+ Routes._logout(req, res);
+ expect(req.logout).toHaveBeenCalled();
+ expect(res.redirect).toHaveBeenCalledWith("/");
+ });
+
+ it("should render failure page if auth failed", function () {
+ res.render = jasmine.createSpy();
+ Routes._authFailed(req, res);
+ expect(res.render).toHaveBeenCalledWith("authFailed");
+ });
+});
View
@@ -0,0 +1,2 @@
+<p>Sorry, authentication failed.</p>
+<p><a href="/">Return to Home Page</a></p>
View
@@ -0,0 +1,8 @@
+{{#if user}}
+ <h2>Welcome, {{user}}!</h2>
+ <p><a href="#">Upload a file (not implemented yet)</a></p>
+ <p><a href="/logout">Sign out</a></p>
+{{else}}
+ <h2>Welcome to the Brackets Extension Registry!</h2>
+ <p>To upload an extension, you must first <a href="/auth/github">sign in via GitHub</a>.</p>
+{{/if}}
View
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Brackets Extension Registry</title>
+ </head>
+ <html>
+ {{{body}}}
+ </html>
+</html>

0 comments on commit 1935a72

Please sign in to comment.