diff --git a/app/routes/github-authorize.js b/app/routes/github-authorize.js index e73d931d778..60e96667d1c 100644 --- a/app/routes/github-authorize.js +++ b/app/routes/github-authorize.js @@ -1,6 +1,19 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; +/** + * This route will be called from the GitHub OAuth flow once the user has + * accepted or rejected the data access permissions. It will forward the + * temporary `code` received from the GitHub API to our own API server which + * will exchange it for an access token. + * + * After the exchange the API will return an API token and the user information. + * The result will be stored and the popup window closed. The `/login` route + * will then continue to evaluate the response. + * + * @see https://developer.github.com/v3/oauth/#github-redirects-back-to-your-site + * @see `/login` route + */ export default Ember.Route.extend({ beforeModel: function(transition) { return ajax('/authorize', {data: transition.queryParams}).then(function(d) { diff --git a/app/routes/github-login.js b/app/routes/github-login.js index 5037585355d..b0e20a5a35c 100644 --- a/app/routes/github-login.js +++ b/app/routes/github-login.js @@ -1,6 +1,19 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; +/** + * Calling this route will query the `/authorize_url` API endpoint + * and redirect to the received URL to initiate the OAuth flow. + * + * Example URL: + * https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg + * + * Once the user has allowed the OAuth flow access the page will redirect him + * to the `/github_authorize` route of this application. + * + * @see https://developer.github.com/v3/oauth/#redirect-users-to-request-github-access + * @see `/github_authorize` route + */ export default Ember.Route.extend({ beforeModel: function() { return ajax('/authorize_url').then(function(url) { diff --git a/app/routes/login.js b/app/routes/login.js index dc7f5d024b2..3a08322d336 100644 --- a/app/routes/login.js +++ b/app/routes/login.js @@ -1,5 +1,12 @@ import Ember from 'ember'; +/** + * This route will open a popup window directed at the `/github_login` route. + * After the window has opened it will wait for the window to close and + * then evaluate whether the OAuth flow was successful. + * + * @see `/github_authorize` route + */ export default Ember.Route.extend({ beforeModel: function(transition) { try { localStorage.removeItem('github_response'); } catch (e) {} @@ -45,4 +52,3 @@ export default Ember.Route.extend({ transition.abort(); } }); - diff --git a/src/keyword.rs b/src/keyword.rs index db7e9fa8582..33fe0304951 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -139,6 +139,7 @@ impl Model for Keyword { fn table_name(_: Option) -> &'static str { "keywords" } } +/// Handles the `GET /keywords` route. pub fn index(req: &mut Request) -> CargoResult { let conn = try!(req.tx()); let (offset, limit) = try!(req.pagination(10, 100)); @@ -175,6 +176,7 @@ pub fn index(req: &mut Request) -> CargoResult { })) } +/// Handles the `GET /keywords/:keyword_id` route. pub fn show(req: &mut Request) -> CargoResult { let name = &req.params()["keyword_id"]; let conn = try!(req.tx()); @@ -185,4 +187,3 @@ pub fn show(req: &mut Request) -> CargoResult { struct R { keyword: EncodableKeyword } Ok(req.json(&R { keyword: kw.encodable() })) } - diff --git a/src/krate.rs b/src/krate.rs index d2f2b948b75..441e3361b34 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -452,6 +452,7 @@ impl Model for Crate { fn table_name(_: Option) -> &'static str { "crates" } } +/// Handles the `GET /crates` route. #[allow(trivial_casts)] pub fn index(req: &mut Request) -> CargoResult { let conn = try!(req.tx()); @@ -574,6 +575,7 @@ pub fn index(req: &mut Request) -> CargoResult { })) } +/// Handles the `GET /summary` route. pub fn summary(req: &mut Request) -> CargoResult { let tx = try!(req.tx()); let num_crates = { @@ -620,6 +622,7 @@ pub fn summary(req: &mut Request) -> CargoResult { })) } +/// Handles the `GET /crates/:crate_id` route. pub fn show(req: &mut Request) -> CargoResult { let name = &req.params()["crate_id"]; let conn = try!(req.tx()); @@ -643,6 +646,7 @@ pub fn show(req: &mut Request) -> CargoResult { })) } +/// Handles the `PUT /crates/new` route. pub fn new(req: &mut Request) -> CargoResult { let app = req.app().clone(); @@ -817,6 +821,7 @@ fn read_fill(r: &mut R, mut slice: &mut [u8]) Ok(()) } +/// Handles the `GET /crates/:crate_id/:version/download` route. pub fn download(req: &mut Request) -> CargoResult { let crate_name = &req.params()["crate_id"]; let version = &req.params()["version"]; @@ -872,6 +877,7 @@ pub fn download(req: &mut Request) -> CargoResult { } } +/// Handles the `GET /crates/:crate_id/downloads` route. pub fn downloads(req: &mut Request) -> CargoResult { let crate_name = &req.params()["crate_id"]; let tx = try!(req.tx()); @@ -931,6 +937,7 @@ fn user_and_crate(req: &mut Request) -> CargoResult<(User, Crate)> { Ok((user.clone(), krate)) } +/// Handles the `PUT /crates/:crate_id/follow` route. pub fn follow(req: &mut Request) -> CargoResult { let (user, krate) = try!(user_and_crate(req)); let tx = try!(req.tx()); @@ -946,6 +953,7 @@ pub fn follow(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } +/// Handles the `DELETE /crates/:crate_id/follow` route. pub fn unfollow(req: &mut Request) -> CargoResult { let (user, krate) = try!(user_and_crate(req)); let tx = try!(req.tx()); @@ -957,6 +965,7 @@ pub fn unfollow(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } +/// Handles the `GET /crates/:crate_id/following` route. pub fn following(req: &mut Request) -> CargoResult { let (user, krate) = try!(user_and_crate(req)); let tx = try!(req.tx()); @@ -968,6 +977,7 @@ pub fn following(req: &mut Request) -> CargoResult { Ok(req.json(&R { following: rows.next().is_some() })) } +/// Handles the `GET /crates/:crate_id/versions` route. pub fn versions(req: &mut Request) -> CargoResult { let crate_name = &req.params()["crate_id"]; let tx = try!(req.tx()); @@ -981,6 +991,7 @@ pub fn versions(req: &mut Request) -> CargoResult { Ok(req.json(&R{ versions: versions })) } +/// Handles the `GET /crates/:crate_id/owners` route. pub fn owners(req: &mut Request) -> CargoResult { let crate_name = &req.params()["crate_id"]; let tx = try!(req.tx()); @@ -993,10 +1004,12 @@ pub fn owners(req: &mut Request) -> CargoResult { Ok(req.json(&R{ users: owners })) } +/// Handles the `PUT /crates/:crate_id/owners` route. pub fn add_owners(req: &mut Request) -> CargoResult { modify_owners(req, true) } +/// Handles the `DELETE /crates/:crate_id/owners` route. pub fn remove_owners(req: &mut Request) -> CargoResult { modify_owners(req, false) } @@ -1054,6 +1067,7 @@ fn modify_owners(req: &mut Request, add: bool) -> CargoResult { Ok(req.json(&R{ ok: true })) } +/// Handles the `GET /crates/:crate_id/reverse_dependencies` route. pub fn reverse_dependencies(req: &mut Request) -> CargoResult { let name = &req.params()["crate_id"]; let conn = try!(req.tx()); diff --git a/src/lib.rs b/src/lib.rs index 6780cbd51dd..6349994baca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ +//! This crate implements the backend server for https://crates.io/ +//! +//! All implemented routes are defined in the [middleware](fn.middleware.html) function and +//! implemented in the [keyword](keyword/index.html), [krate](krate/index.html), +//! [user](user/index.html) and [version](version/index.html) modules. + #[macro_use] extern crate log; extern crate postgres as pg; extern crate rustc_serialize; diff --git a/src/user/mod.rs b/src/user/mod.rs index 32750866d64..85c10d54881 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -136,7 +136,23 @@ impl Model for User { fn table_name(_: Option) -> &'static str { "users" } } +/// Handles the `GET /authorize_url` route. +/// +/// This route will return an authorization URL for the GitHub OAuth flow including the crates.io +/// `client_id` and a randomly generated `state` secret. +/// +/// see https://developer.github.com/v3/oauth/#redirect-users-to-request-github-access +/// +/// ## Response Body Example +/// +/// ```json +/// { +/// "state": "b84a63c4ea3fcb4ac84", +/// "url": "https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg" +/// } +/// ``` pub fn github_authorize(req: &mut Request) -> CargoResult { + // Generate a random 16 char ASCII string let state: String = thread_rng().gen_ascii_chars().take(16).collect(); req.session().insert("github_oauth_state".to_string(), state.clone()); @@ -147,6 +163,34 @@ pub fn github_authorize(req: &mut Request) -> CargoResult { Ok(req.json(&R { url: url.to_string(), state: state })) } +/// Handles the `GET /authorize` route. +/// +/// This route is called from the GitHub API OAuth flow after the user accepted or rejected +/// the data access permissions. It will check the `state` parameter and then call the GitHub API +/// to exchange the temporary `code` for an API token. The API token is returned together with +/// the corresponding user information. +/// +/// see https://developer.github.com/v3/oauth/#github-redirects-back-to-your-site +/// +/// ## Query Parameters +/// +/// - `code` – temporary code received from the GitHub API **(Required)** +/// - `state` – state parameter received from the GitHub API **(Required)** +/// +/// ## Response Body Example +/// +/// ```json +/// { +/// "api_token": "b84a63c4ea3fcb4ac84", +/// "user": { +/// "email": "foo@bar.org", +/// "name": "Foo Bar", +/// "login": "foobar", +/// "avatar": "https://avatars.githubusercontent.com/u/1234", +/// "url": null +/// } +/// } +/// ``` pub fn github_access_token(req: &mut Request) -> CargoResult { // Parse the url query let mut query = req.query(); @@ -197,11 +241,13 @@ pub fn github_access_token(req: &mut Request) -> CargoResult { me(req) } +/// Handles the `GET /logout` route. pub fn logout(req: &mut Request) -> CargoResult { req.session().remove(&"user_id".to_string()); Ok(req.json(&true)) } +/// Handles the `GET /me/reset_token` route. pub fn reset_token(req: &mut Request) -> CargoResult { let user = try!(req.user()); @@ -215,6 +261,7 @@ pub fn reset_token(req: &mut Request) -> CargoResult { Ok(req.json(&R { api_token: token })) } +/// Handles the `GET /me` route. pub fn me(req: &mut Request) -> CargoResult { let user = try!(req.user()); @@ -224,6 +271,7 @@ pub fn me(req: &mut Request) -> CargoResult { Ok(req.json(&R{ user: user.clone().encodable(), api_token: token })) } +/// Handles the `GET /me/updates` route. pub fn updates(req: &mut Request) -> CargoResult { let user = try!(req.user()); let (offset, limit) = try!(req.pagination(10, 100)); diff --git a/src/version.rs b/src/version.rs index 58008d1d5fd..c4fc40d8d05 100644 --- a/src/version.rs +++ b/src/version.rs @@ -210,6 +210,7 @@ impl Model for Version { fn table_name(_: Option) -> &'static str { "versions" } } +/// Handles the `GET /versions` route. pub fn index(req: &mut Request) -> CargoResult { let conn = try!(req.tx()); @@ -247,6 +248,7 @@ pub fn index(req: &mut Request) -> CargoResult { Ok(req.json(&R { versions: versions })) } +/// Handles the `GET /versions/:version_id` route. pub fn show(req: &mut Request) -> CargoResult { let (version, krate) = match req.params().find("crate_id") { Some(..) => try!(version_and_crate(req)), @@ -281,6 +283,7 @@ fn version_and_crate(req: &mut Request) -> CargoResult<(Version, Crate)> { Ok((version, krate)) } +/// Handles the `GET /crates/:crate_id/:version/dependencies` route. pub fn dependencies(req: &mut Request) -> CargoResult { let (version, _) = try!(version_and_crate(req)); let tx = try!(req.tx()); @@ -294,6 +297,7 @@ pub fn dependencies(req: &mut Request) -> CargoResult { Ok(req.json(&R{ dependencies: deps })) } +/// Handles the `GET /crates/:crate_id/:version/downloads` route. pub fn downloads(req: &mut Request) -> CargoResult { let (version, _) = try!(version_and_crate(req)); @@ -313,6 +317,7 @@ pub fn downloads(req: &mut Request) -> CargoResult { Ok(req.json(&R{ version_downloads: downloads })) } +/// Handles the `GET /crates/:crate_id/:version/authors` route. pub fn authors(req: &mut Request) -> CargoResult { let (version, _) = try!(version_and_crate(req)); let tx = try!(req.tx()); @@ -331,10 +336,12 @@ pub fn authors(req: &mut Request) -> CargoResult { Ok(req.json(&R{ users: users, meta: Meta { names: names } })) } +/// Handles the `DELETE /crates/:crate_id/:version/yank` route. pub fn yank(req: &mut Request) -> CargoResult { modify_yank(req, true) } +/// Handles the `PUT /crates/:crate_id/:version/unyank` route. pub fn unyank(req: &mut Request) -> CargoResult { modify_yank(req, false) }