basic token generation

main
Hannaeko 2021-03-27 01:45:59 -04:00
parent 84b1ce0823
commit 2b8df138e0
7 changed files with 446 additions and 33 deletions

263
Cargo.lock generated
View File

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.3.2"
@ -54,6 +56,27 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.48"
@ -110,6 +133,17 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -119,12 +153,24 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -146,6 +192,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi 0.3.9",
]
@ -159,6 +206,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cookie"
version = "0.11.4"
@ -187,6 +240,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crossbeam-utils"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "crypto-mac"
version = "0.10.0"
@ -288,6 +352,20 @@ dependencies = [
"generic-array",
]
[[package]]
name = "djangohashers"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b101df5b2cab337a012f1a43d4a48810a81c10ea3b6bc73b456f5707c7b4325"
dependencies = [
"base64 0.13.0",
"constant_time_eq",
"lazy_static",
"rand",
"regex",
"rust-argon2",
]
[[package]]
name = "endian-type"
version = "0.1.2"
@ -591,6 +669,29 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonwebtoken"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32"
dependencies = [
"base64 0.12.3",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -774,8 +875,11 @@ name = "nomilo"
version = "0.1.0-dev"
dependencies = [
"base64 0.13.0",
"chrono",
"diesel",
"diesel-derive-enum",
"djangohashers",
"jsonwebtoken",
"rocket",
"rocket_contrib",
"serde",
@ -813,6 +917,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -842,6 +957,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -895,6 +1016,17 @@ dependencies = [
"yansi",
]
[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
"base64 0.13.0",
"once_cell",
"regex",
]
[[package]]
name = "percent-encoding"
version = "1.0.1"
@ -1048,6 +1180,38 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi 0.3.9",
]
[[package]]
name = "rocket"
version = "0.4.7"
@ -1129,6 +1293,18 @@ dependencies = [
"unicode-xid 0.1.0",
]
[[package]]
name = "rust-argon2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64 0.13.0",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -1209,6 +1385,17 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "simple_asn1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint",
"num-traits",
]
[[package]]
name = "slab"
version = "0.4.2"
@ -1232,6 +1419,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "state"
version = "0.4.2"
@ -1460,6 +1653,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "1.7.2"
@ -1528,6 +1727,70 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
dependencies = [
"bumpalo",
"lazy_static",
"log 0.4.14",
"proc-macro2 1.0.24",
"quote 1.0.9",
"syn 1.0.64",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
dependencies = [
"quote 1.0.9",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.9",
"syn 1.0.64",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
[[package]]
name = "web-sys"
version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.2.8"

View File

@ -11,10 +11,13 @@ trust-dns-client = "0.20.1"
trust-dns-proto = "0.20.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rocket = "0.4.7"
rocket = "0.4"
rocket_contrib = { version = "0.4", default-features = false, features = ["json", "diesel_sqlite_pool"]}
toml = "0.5"
base64 = "0.13.0"
uuid = { version = "0.8.2", features = ["v4", "serde"] }
diesel = { version = "1.4", features = ["sqlite"] }
diesel-derive-enum = { version = "1", features = ["sqlite"] }
djangohashers = { version = "1.4.0", features = ["with_argon2"], default-features = false }
jsonwebtoken = "7.2.0"
chrono = { version = "0.4", features = ["serde"] }

View File

@ -1,7 +1,7 @@
-- Your SQL goes here
CREATE TABLE localuser (
user_id VARCHAR NOT NULL PRIMARY KEY,
username VARCHAR NOT NULL,
username VARCHAR NOT NULL UNIQUE,
password VARCHAR NOT NULL,
FOREIGN KEY(user_id) REFERENCES user(id)
);

View File

@ -1,15 +1,26 @@
use serde::{Serialize, Deserialize};
use rocket_contrib::json::Json;
use diesel::prelude::*;
use rocket::Response;
use rocket::http::Status;
use uuid::Uuid;
use jsonwebtoken::{encode, Header, EncodingKey};
use chrono::prelude::{DateTime, Utc};
use chrono::Duration;
use chrono::serde::ts_seconds;
use crate::DbConn;
use crate::models::users::{UserInfo, LocalUser, User};
use crate::models::errors::ErrorResponse;
use crate::models::users::{LocalUser, CreateUserRequest};
#[derive(Debug, Serialize, Deserialize)]
struct AuthClaims {
jti: String,
sub: String,
exp: usize,
iat: usize,
#[serde(with = "ts_seconds")]
exp: DateTime<Utc>,
#[serde(with = "ts_seconds")]
iat: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
@ -19,26 +30,35 @@ pub struct AuthTokenResponse {
#[derive(Debug, Deserialize)]
pub struct AuthTokenRequest {
user: String,
username: String,
password: String,
}
#[post("/users/me/token", data = "<auth_request>")]
pub fn create_auth_token(conn: DbConn, auth_request: Json<AuthTokenRequest>) -> Json<AuthTokenResponse> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
pub fn create_auth_token(conn: DbConn, auth_request: Json<AuthTokenRequest>) -> Result<Json<AuthTokenResponse>, ErrorResponse<()>> {
let user_info = LocalUser::get_user_by_creds(&conn, &auth_request.username, &auth_request.password)?;
let jti = Uuid::new_v4().to_simple().to_string();
let iat = Utc::now();
let exp = iat + Duration::minutes(1);
let client_user: Result<(User, LocalUser), _> = user.inner_join(localuser).filter(username.eq(&auth_request.user)).get_result(&*conn);
println!("{:?}", client_user);
Json(AuthTokenResponse { token: "".into() })
let claims = AuthClaims {
jti: jti,
sub: user_info.id,
exp: exp,
iat: iat,
};
// TODO: catch error
let token = encode(&Header::default(), &claims, &EncodingKey::from_secret("changeme".as_ref())).unwrap();
Ok(Json(AuthTokenResponse { token }))
}
/*
GET /users -> list all users
POST /users
{
provider: local
...
#[post("/users", data = "<user_request>")]
pub fn create_user<'r>(conn: DbConn, user_request: Json<CreateUserRequest>) -> Result<Response<'r>, ErrorResponse<()>>{
// TODO: Check current user if any to check if user has permission to create users (with or without role)
let _user_info = LocalUser::create_user(&conn, user_request.into_inner())?;
Response::build()
.status(Status::Created)
.ok()
}
/users/<uuid or me>/
*/

View File

@ -8,9 +8,6 @@ use rocket::State;
use rocket::http::Status;
use rocket_contrib::json::Json;
use rocket_contrib::databases::diesel as rocket_diesel;
use diesel::prelude::*;
use trust_dns_client::client::{Client, SyncClient};
use trust_dns_client::tcp::TcpClientConnection;
@ -66,5 +63,5 @@ fn main() {
rocket::ignite()
.manage(client)
.attach(DbConn::fairing())
.mount("/api/v1", routes![get_zone_records, create_auth_token]).launch();
.mount("/api/v1", routes![get_zone_records, create_auth_token, create_user]).launch();
}

View File

@ -3,7 +3,7 @@ use rocket::http::Status;
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket_contrib::json::Json;
use crate::models::users::UserError;
#[derive(Serialize, Debug)]
pub struct ErrorResponse<T> {
@ -53,3 +53,19 @@ impl<'r, T: Serialize> Responder<'r> for ErrorResponse<T> {
Response::build_from(Json(self).respond_to(req)?).status(status).ok()
}
}
impl From<UserError> for ErrorResponse<()> {
fn from(e: UserError) -> Self {
match e {
UserError::NotFound => ErrorResponse::new(Status::Unauthorized, "Incorrect password or username.".into()),
UserError::UserExists => ErrorResponse::new(Status::Conflict, "User already exists.".into()),
UserError::DbError(e) => make_500(e),
UserError::PasswordError(e) => make_500(e)
}
}
}
fn make_500<E: std::fmt::Debug>(e: E) -> ErrorResponse<()> {
println!("{:?}", e);
ErrorResponse::new(Status::InternalServerError, "An unexpected error occured.".into())
}

View File

@ -1,25 +1,32 @@
use uuid::Uuid;
use diesel::prelude::*;
use diesel::associations::HasTable;
use diesel::result::Error as DieselError;
use rocket::request::{FromRequest, Request, Outcome};
use diesel_derive_enum::DbEnum;
use serde::Deserialize;
// TODO: Maybe just use argon2 crate directly
use djangohashers::{make_password_with_algorithm, check_password, HasherError, Algorithm};
use crate::schema::*;
use crate::DbConn;
#[derive(Debug, DbEnum)]
#[derive(Debug, DbEnum, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Role {
Admin,
ZoneAdmin,
}
#[derive(Debug, Queryable, Identifiable)]
// TODO: Store Uuid instead of string??
// TODO: Store role as Role and not String.
#[derive(Debug, Queryable, Identifiable, Insertable)]
#[table_name = "user"]
pub struct User {
pub id: String,
pub role: String,
}
#[derive(Debug, Queryable, Identifiable)]
#[derive(Debug, Queryable, Identifiable, Insertable)]
#[table_name = "localuser"]
#[primary_key(user_id)]
pub struct LocalUser {
@ -28,21 +35,128 @@ pub struct LocalUser {
pub password: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub password: String,
pub email: String,
pub role: Option<Role>
}
// pub struct LdapUserAssociation {
// user_id: Uuid,
// ldap_id: String
// }
#[derive(Debug)]
pub struct UserInfo {
id: Uuid,
role: Role,
username: String,
pub id: String,
pub role: String,
pub username: String,
}
impl<'a, 'r> FromRequest<'a, 'r> for UserInfo {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<UserInfo, ()> {
// LocalUser::get_user_by_uuid()
Outcome::Forward(())
}
}
#[derive(Debug)]
pub enum UserError {
NotFound,
UserExists,
DbError(DieselError),
PasswordError(HasherError),
}
impl From<DieselError> for UserError {
fn from(e: DieselError) -> Self {
match e {
DieselError::NotFound => UserError::NotFound,
DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserExists,
other => UserError::DbError(other)
}
}
}
impl From<HasherError> for UserError {
fn from(e: HasherError) -> Self {
match e {
other => UserError::PasswordError(other)
}
}
}
impl LocalUser {
pub fn create_user(conn: &DbConn, user_request: CreateUserRequest) -> Result<UserInfo, UserError> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
let new_user_id = Uuid::new_v4().to_simple().to_string();
let new_user = User {
id: new_user_id.clone(),
// TODO: Use role from request
role: "zoneadmin".into(),
};
let new_localuser = LocalUser {
user_id: new_user_id.clone(),
username: user_request.username.clone(),
password: make_password_with_algorithm(&user_request.password, Algorithm::Argon2),
};
let res = UserInfo {
id: new_user.id.clone(),
role: new_user.role.clone(),
username: new_localuser.username.clone(),
};
conn.immediate_transaction(|| -> diesel::QueryResult<()> {
diesel::insert_into(user)
.values(new_user)
.execute(&**conn)?;
diesel::insert_into(localuser)
.values(new_localuser)
.execute(&**conn)?;
Ok(())
})?;
Ok(res)
}
pub fn get_user_by_creds(
conn: &DbConn,
request_username: &str,
request_password: &str
) -> Result<UserInfo, UserError> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
.filter(username.eq(request_username))
.get_result(&**conn)?;
if !check_password(&request_password, &client_localuser.password)? {
return Err(UserError::NotFound);
}
Ok(UserInfo {
id: client_user.id,
role: client_user.role,
username: client_localuser.username,
})
}
pub fn get_user_by_uuid(user_id: Uuid) -> Result<UserInfo, ()> {
unimplemented!()
}
}