Skip to content
This repository has been archived by the owner on Sep 2, 2021. It is now read-only.

Commit

Permalink
Initial app skeleton with github authentication working.
Browse files Browse the repository at this point in the history
  • Loading branch information
Narciso Jaramillo committed Mar 27, 2013
1 parent 5d123ed commit 1935a72
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config/
node_modules/
npm-debug.log
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
97 changes: 97 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -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);
86 changes: 86 additions & 0 deletions lib/Routes.js
Original file line number Diff line number Diff line change
@@ -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;
42 changes: 27 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
75 changes: 75 additions & 0 deletions spec/Routes.spec.js
Original file line number Diff line number Diff line change
@@ -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");
});
});
2 changes: 2 additions & 0 deletions views/authFailed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<p>Sorry, authentication failed.</p>
<p><a href="/">Return to Home Page</a></p>
8 changes: 8 additions & 0 deletions views/index.html
Original file line number Diff line number Diff line change
@@ -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}}
9 changes: 9 additions & 0 deletions views/layout.html
Original file line number Diff line number Diff line change
@@ -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.