use db::models::*;
use diesel::result::Error as DieselError;
use diesel::pg::PgConnection as Connection;
use rocket::http::Cookies;
use rocket::request;
use std::{env, fmt};
use html::error::Error;
use super::github;
use super::rocket;
static AUTH_COOKIE_NAME: &'static str = "clowder_user";
struct Authenticator {
conn: Connection,
}
impl Authenticator {
fn new() -> Authenticator {
Authenticator { conn: super::db::establish_connection() }
}
fn authenticate(self, cookies: &mut Cookies) -> Result<AuthContext, Error> {
let user = cookies.get_private(AUTH_COOKIE_NAME)
.ok_or(Error::AuthRequired)
.and_then(|ref username| self.lookup_user(username.value()))
.or_else(|_| self.try_fake_auth())?;
Ok(AuthContext {
conn: self.conn,
user: user,
})
}
fn lookup_user(&self, clowder_username: &str) -> Result<User, Error> {
User::with_username(&clowder_username, &self.conn).map_err(Error::DatabaseError)
}
fn retrieve_github_user(&self, auth_code: String) -> Result<User, Error> {
let gh_username = github::auth_callback(auth_code)?;
GithubAccount::get(&gh_username, &self.conn)
.map(|(_, user)| user)
.map_err(|_| Error::AuthError(format!["GitHub user '{}' not authorized", gh_username]))
}
fn try_fake_auth(&self) -> Result<User, Error> {
env::var_os("CLOWDER_FAKE_GITHUB_USERNAME")
.and_then(|s| s.to_str().map(str::to_string))
.ok_or(Error::AuthRequired)
.and_then(|username| {
GithubAccount::get(&username, &self.conn)
.map_err(Error::DatabaseError)
.map(|(_, user)| user)
})
}
}
pub struct AuthContext {
pub conn: Connection,
pub user: User,
}
impl fmt::Debug for AuthContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write![f, "AuthContext for User '{:?}'", self.user]
}
}
impl<'a, 'r> request::FromRequest<'a, 'r> for AuthContext {
type Error = super::error::Error;
fn from_request(req: &'a request::Request<'r>) -> request::Outcome<AuthContext, super::Error> {
let auth_context = Authenticator::new().authenticate(&mut req.cookies());
match auth_context {
Ok(ctx) => rocket::outcome::Outcome::Success(ctx),
Err(e) => {
let failure = match e {
Error::AuthRequired => (rocket::http::Status::Unauthorized, e),
Error::DatabaseError(DieselError::NotFound) => {
(rocket::http::Status::Forbidden, e)
},
_ => (rocket::http::Status::InternalServerError, e),
};
rocket::outcome::Outcome::Failure(failure)
}
}
}
}
pub fn github_callback(code: String, cookies: rocket::http::Cookies) -> Result<(), Error> {
Authenticator::new()
.retrieve_github_user(code)
.map(|user| set_user_cookie(cookies, user.username))
}
pub fn logout<'c>(mut jar: Cookies) {
jar.get_private(AUTH_COOKIE_NAME).map(|c| jar.remove_private(c));
}
pub fn set_user_cookie<'c, S: Into<String>>(mut jar: Cookies, username: S) {
jar.add_private(rocket::http::Cookie::new(String::from(AUTH_COOKIE_NAME), username.into()))
}