geforkt von mirrored/vaultwarden
Updated bw_rs to Rocket version 0.4-rc1
Dieser Commit ist enthalten in:
Ursprung
f1b1000600
Commit
c673370103
31 geänderte Dateien mit 716 neuen und 1485 gelöschten Zeilen
879
Cargo.lock
generiert
879
Cargo.lock
generiert
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
30
Cargo.toml
30
Cargo.toml
|
@ -5,18 +5,17 @@ authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Web framework for nightly with a focus on ease-of-use, expressibility, and speed.
|
# Web framework for nightly with a focus on ease-of-use, expressibility, and speed.
|
||||||
rocket = { version = "0.3.17", features = ["tls"] }
|
rocket = { version = "0.4.0-rc.1", features = ["tls"] }
|
||||||
rocket_codegen = "0.3.17"
|
rocket_contrib = "0.4.0-rc.1"
|
||||||
rocket_contrib = "0.3.17"
|
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
reqwest = "0.9.2"
|
reqwest = "0.9.4"
|
||||||
|
|
||||||
# multipart/form-data support
|
# multipart/form-data support
|
||||||
multipart = "0.15.3"
|
multipart = "0.15.3"
|
||||||
|
|
||||||
# WebSockets library
|
# WebSockets library
|
||||||
ws = "0.7.8"
|
ws = "0.7.9"
|
||||||
|
|
||||||
# MessagePack library
|
# MessagePack library
|
||||||
rmpv = "0.4.0"
|
rmpv = "0.4.0"
|
||||||
|
@ -25,9 +24,9 @@ rmpv = "0.4.0"
|
||||||
chashmap = "2.2.0"
|
chashmap = "2.2.0"
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
serde = "1.0.79"
|
serde = "1.0.80"
|
||||||
serde_derive = "1.0.79"
|
serde_derive = "1.0.80"
|
||||||
serde_json = "1.0.31"
|
serde_json = "1.0.32"
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
diesel = { version = "1.3.3", features = ["sqlite", "chrono", "r2d2"] }
|
diesel = { version = "1.3.3", features = ["sqlite", "chrono", "r2d2"] }
|
||||||
|
@ -37,7 +36,7 @@ diesel_migrations = { version = "1.3.0", features = ["sqlite"] }
|
||||||
libsqlite3-sys = { version = "0.9.3", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.9.3", features = ["bundled"] }
|
||||||
|
|
||||||
# Crypto library
|
# Crypto library
|
||||||
ring = { version = "= 0.11.0", features = ["rsa_signing"] }
|
ring = { version = "0.13.2", features = ["rsa_signing"] }
|
||||||
|
|
||||||
# UUID generation
|
# UUID generation
|
||||||
uuid = { version = "0.7.1", features = ["v4"] }
|
uuid = { version = "0.7.1", features = ["v4"] }
|
||||||
|
@ -52,7 +51,7 @@ oath = "0.10.2"
|
||||||
data-encoding = "2.1.1"
|
data-encoding = "2.1.1"
|
||||||
|
|
||||||
# JWT library
|
# JWT library
|
||||||
jsonwebtoken = "= 4.0.1"
|
jsonwebtoken = "5.0.1"
|
||||||
|
|
||||||
# U2F library
|
# U2F library
|
||||||
u2f = "0.1.2"
|
u2f = "0.1.2"
|
||||||
|
@ -70,17 +69,18 @@ num-derive = "0.2.3"
|
||||||
# Email libraries
|
# Email libraries
|
||||||
lettre = "0.9.0"
|
lettre = "0.9.0"
|
||||||
lettre_email = "0.9.0"
|
lettre_email = "0.9.0"
|
||||||
native-tls = "0.2.1"
|
native-tls = "0.2.2"
|
||||||
|
|
||||||
# Number encoding library
|
# Number encoding library
|
||||||
byteorder = "1.2.6"
|
byteorder = "1.2.7"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Make jwt use ring 0.11, to match rocket
|
# Add support for Timestamp type
|
||||||
jsonwebtoken = { path = "libs/jsonwebtoken" }
|
|
||||||
rmp = { git = 'https://github.com/dani-garcia/msgpack-rust' }
|
rmp = { git = 'https://github.com/dani-garcia/msgpack-rust' }
|
||||||
|
|
||||||
|
# Use new native_tls version 0.2
|
||||||
lettre = { git = 'https://github.com/lettre/lettre', rev = 'c988b1760ad81' }
|
lettre = { git = 'https://github.com/lettre/lettre', rev = 'c988b1760ad81' }
|
||||||
lettre_email = { git = 'https://github.com/lettre/lettre', rev = 'c988b1760ad81' }
|
lettre_email = { git = 'https://github.com/lettre/lettre', rev = 'c988b1760ad81' }
|
||||||
|
|
||||||
# Version 0.1.2 from crates.io lacks a commit that fixes a certificate error
|
# Version 0.1.2 from crates.io lacks a commit that fixes a certificate error
|
||||||
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '193de35093a44' }
|
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '75b9fa5afb4c5' }
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "jsonwebtoken"
|
|
||||||
version = "4.0.1"
|
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
|
||||||
license = "MIT"
|
|
||||||
readme = "README.md"
|
|
||||||
description = "Create and parse JWT in a strongly typed way."
|
|
||||||
homepage = "https://github.com/Keats/rust-jwt"
|
|
||||||
repository = "https://github.com/Keats/rust-jwt"
|
|
||||||
keywords = ["jwt", "web", "api", "token", "json"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
error-chain = { version = "0.11", default-features = false }
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
ring = { version = "0.11.0", features = ["rsa_signing", "dev_urandom_fallback"] }
|
|
||||||
base64 = "0.9"
|
|
||||||
untrusted = "0.5"
|
|
||||||
chrono = "0.4"
|
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Vincent Prouillet
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,120 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use base64;
|
|
||||||
use ring::{rand, digest, hmac, signature};
|
|
||||||
use ring::constant_time::verify_slices_are_equal;
|
|
||||||
use untrusted;
|
|
||||||
|
|
||||||
use errors::{Result, ErrorKind};
|
|
||||||
|
|
||||||
|
|
||||||
/// The algorithms supported for signing/verifying
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum Algorithm {
|
|
||||||
/// HMAC using SHA-256
|
|
||||||
HS256,
|
|
||||||
/// HMAC using SHA-384
|
|
||||||
HS384,
|
|
||||||
/// HMAC using SHA-512
|
|
||||||
HS512,
|
|
||||||
|
|
||||||
/// RSASSA-PKCS1-v1_5 using SHA-256
|
|
||||||
RS256,
|
|
||||||
/// RSASSA-PKCS1-v1_5 using SHA-384
|
|
||||||
RS384,
|
|
||||||
/// RSASSA-PKCS1-v1_5 using SHA-512
|
|
||||||
RS512,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The actual HS signing + encoding
|
|
||||||
fn sign_hmac(alg: &'static digest::Algorithm, key: &[u8], signing_input: &str) -> Result<String> {
|
|
||||||
let signing_key = hmac::SigningKey::new(alg, key);
|
|
||||||
let digest = hmac::sign(&signing_key, signing_input.as_bytes());
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
base64::encode_config::<hmac::Signature>(&digest, base64::URL_SAFE_NO_PAD)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The actual RSA signing + encoding
|
|
||||||
/// Taken from Ring doc https://briansmith.org/rustdoc/ring/signature/index.html
|
|
||||||
fn sign_rsa(alg: Algorithm, key: &[u8], signing_input: &str) -> Result<String> {
|
|
||||||
let ring_alg = match alg {
|
|
||||||
Algorithm::RS256 => &signature::RSA_PKCS1_SHA256,
|
|
||||||
Algorithm::RS384 => &signature::RSA_PKCS1_SHA384,
|
|
||||||
Algorithm::RS512 => &signature::RSA_PKCS1_SHA512,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_pair = Arc::new(
|
|
||||||
signature::RSAKeyPair::from_der(untrusted::Input::from(key))
|
|
||||||
.map_err(|_| ErrorKind::InvalidKey)?
|
|
||||||
);
|
|
||||||
let mut signing_state = signature::RSASigningState::new(key_pair)
|
|
||||||
.map_err(|_| ErrorKind::InvalidKey)?;
|
|
||||||
let mut signature = vec![0; signing_state.key_pair().public_modulus_len()];
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
signing_state.sign(ring_alg, &rng, signing_input.as_bytes(), &mut signature)
|
|
||||||
.map_err(|_| ErrorKind::InvalidKey)?;
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
base64::encode_config::<[u8]>(&signature, base64::URL_SAFE_NO_PAD)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take the payload of a JWT, sign it using the algorithm given and return
|
|
||||||
/// the base64 url safe encoded of the result.
|
|
||||||
///
|
|
||||||
/// Only use this function if you want to do something other than JWT.
|
|
||||||
pub fn sign(signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
|
|
||||||
match algorithm {
|
|
||||||
Algorithm::HS256 => sign_hmac(&digest::SHA256, key, signing_input),
|
|
||||||
Algorithm::HS384 => sign_hmac(&digest::SHA384, key, signing_input),
|
|
||||||
Algorithm::HS512 => sign_hmac(&digest::SHA512, key, signing_input),
|
|
||||||
|
|
||||||
Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => sign_rsa(algorithm, key, signing_input),
|
|
||||||
// TODO: if PKCS1 is made prublic, remove the line above and uncomment below
|
|
||||||
// Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input),
|
|
||||||
// Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input),
|
|
||||||
// Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See Ring RSA docs for more details
|
|
||||||
fn verify_rsa(alg: &signature::RSAParameters, signature: &str, signing_input: &str, key: &[u8]) -> Result<bool> {
|
|
||||||
let signature_bytes = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)?;
|
|
||||||
let public_key_der = untrusted::Input::from(key);
|
|
||||||
let message = untrusted::Input::from(signing_input.as_bytes());
|
|
||||||
let expected_signature = untrusted::Input::from(signature_bytes.as_slice());
|
|
||||||
|
|
||||||
let res = signature::verify(alg, public_key_der, message, expected_signature);
|
|
||||||
|
|
||||||
Ok(res.is_ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares the signature given with a re-computed signature for HMAC or using the public key
|
|
||||||
/// for RSA.
|
|
||||||
///
|
|
||||||
/// Only use this function if you want to do something other than JWT.
|
|
||||||
///
|
|
||||||
/// `signature` is the signature part of a jwt (text after the second '.')
|
|
||||||
///
|
|
||||||
/// `signing_input` is base64(header) + "." + base64(claims)
|
|
||||||
pub fn verify(signature: &str, signing_input: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
|
|
||||||
match algorithm {
|
|
||||||
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
|
|
||||||
// we just re-sign the data with the key and compare if they are equal
|
|
||||||
let signed = sign(signing_input, key, algorithm)?;
|
|
||||||
Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
|
|
||||||
},
|
|
||||||
Algorithm::RS256 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA256, signature, signing_input, key),
|
|
||||||
Algorithm::RS384 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA384, signature, signing_input, key),
|
|
||||||
Algorithm::RS512 => verify_rsa(&signature::RSA_PKCS1_2048_8192_SHA512, signature, signing_input, key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Algorithm {
|
|
||||||
fn default() -> Self {
|
|
||||||
Algorithm::HS256
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
use base64;
|
|
||||||
use serde_json;
|
|
||||||
use ring;
|
|
||||||
|
|
||||||
error_chain! {
|
|
||||||
errors {
|
|
||||||
/// When a token doesn't have a valid JWT shape
|
|
||||||
InvalidToken {
|
|
||||||
description("invalid token")
|
|
||||||
display("Invalid token")
|
|
||||||
}
|
|
||||||
/// When the signature doesn't match
|
|
||||||
InvalidSignature {
|
|
||||||
description("invalid signature")
|
|
||||||
display("Invalid signature")
|
|
||||||
}
|
|
||||||
/// When the secret given is not a valid RSA key
|
|
||||||
InvalidKey {
|
|
||||||
description("invalid key")
|
|
||||||
display("Invalid Key")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation error
|
|
||||||
|
|
||||||
/// When a token’s `exp` claim indicates that it has expired
|
|
||||||
ExpiredSignature {
|
|
||||||
description("expired signature")
|
|
||||||
display("Expired Signature")
|
|
||||||
}
|
|
||||||
/// When a token’s `iss` claim does not match the expected issuer
|
|
||||||
InvalidIssuer {
|
|
||||||
description("invalid issuer")
|
|
||||||
display("Invalid Issuer")
|
|
||||||
}
|
|
||||||
/// When a token’s `aud` claim does not match one of the expected audience values
|
|
||||||
InvalidAudience {
|
|
||||||
description("invalid audience")
|
|
||||||
display("Invalid Audience")
|
|
||||||
}
|
|
||||||
/// When a token’s `aud` claim does not match one of the expected audience values
|
|
||||||
InvalidSubject {
|
|
||||||
description("invalid subject")
|
|
||||||
display("Invalid Subject")
|
|
||||||
}
|
|
||||||
/// When a token’s `iat` claim is in the future
|
|
||||||
InvalidIssuedAt {
|
|
||||||
description("invalid issued at")
|
|
||||||
display("Invalid Issued At")
|
|
||||||
}
|
|
||||||
/// When a token’s nbf claim represents a time in the future
|
|
||||||
ImmatureSignature {
|
|
||||||
description("immature signature")
|
|
||||||
display("Immature Signature")
|
|
||||||
}
|
|
||||||
/// When the algorithm in the header doesn't match the one passed to `decode`
|
|
||||||
InvalidAlgorithm {
|
|
||||||
description("Invalid algorithm")
|
|
||||||
display("Invalid Algorithm")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreign_links {
|
|
||||||
Unspecified(ring::error::Unspecified) #[doc = "An error happened while signing/verifying a token with RSA"];
|
|
||||||
Base64(base64::DecodeError) #[doc = "An error happened while decoding some base64 text"];
|
|
||||||
Json(serde_json::Error) #[doc = "An error happened while serializing/deserializing JSON"];
|
|
||||||
Utf8(::std::string::FromUtf8Error) #[doc = "An error happened while trying to convert the result of base64 decoding to a String"];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
use crypto::Algorithm;
|
|
||||||
|
|
||||||
|
|
||||||
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
|
|
||||||
/// set to `JWT`. All the other fields are optional.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Header {
|
|
||||||
/// The type of JWS: it can only be "JWT" here
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub typ: Option<String>,
|
|
||||||
/// The algorithm used
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1).
|
|
||||||
pub alg: Algorithm,
|
|
||||||
/// Content type
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7519#5.2](https://tools.ietf.org/html/rfc7519#section-5.2).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub cty: Option<String>,
|
|
||||||
/// JSON Key URL
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.2](https://tools.ietf.org/html/rfc7515#section-4.1.2).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub jku: Option<String>,
|
|
||||||
/// Key ID
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.4](https://tools.ietf.org/html/rfc7515#section-4.1.4).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub kid: Option<String>,
|
|
||||||
/// X.509 URL
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.5](https://tools.ietf.org/html/rfc7515#section-4.1.5).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub x5u: Option<String>,
|
|
||||||
/// X.509 certificate thumbprint
|
|
||||||
///
|
|
||||||
/// Defined in [RFC7515#4.1.7](https://tools.ietf.org/html/rfc7515#section-4.1.7).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub x5t: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
/// Returns a JWT header with the algorithm given
|
|
||||||
pub fn new(algorithm: Algorithm) -> Header {
|
|
||||||
Header {
|
|
||||||
typ: Some("JWT".to_string()),
|
|
||||||
alg: algorithm,
|
|
||||||
cty: None,
|
|
||||||
jku: None,
|
|
||||||
kid: None,
|
|
||||||
x5u: None,
|
|
||||||
x5t: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Header {
|
|
||||||
/// Returns a JWT header using the default Algorithm, HS256
|
|
||||||
fn default() -> Self {
|
|
||||||
Header::new(Algorithm::default())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
//! Create and parses JWT (JSON Web Tokens)
|
|
||||||
//!
|
|
||||||
//! Documentation: [stable](https://docs.rs/jsonwebtoken/)
|
|
||||||
#![recursion_limit = "300"]
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
#![allow(unused_doc_comments)]
|
|
||||||
#![allow(renamed_and_removed_lints)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate error_chain;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate base64;
|
|
||||||
extern crate ring;
|
|
||||||
extern crate untrusted;
|
|
||||||
extern crate chrono;
|
|
||||||
|
|
||||||
/// All the errors, generated using error-chain
|
|
||||||
pub mod errors;
|
|
||||||
mod header;
|
|
||||||
mod crypto;
|
|
||||||
mod serialization;
|
|
||||||
mod validation;
|
|
||||||
|
|
||||||
pub use header::Header;
|
|
||||||
pub use crypto::{
|
|
||||||
Algorithm,
|
|
||||||
sign,
|
|
||||||
verify,
|
|
||||||
};
|
|
||||||
pub use validation::Validation;
|
|
||||||
pub use serialization::TokenData;
|
|
||||||
|
|
||||||
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
|
|
||||||
use errors::{Result, ErrorKind};
|
|
||||||
use serialization::{from_jwt_part, from_jwt_part_claims, to_jwt_part};
|
|
||||||
use validation::{validate};
|
|
||||||
|
|
||||||
|
|
||||||
/// Encode the header and claims given and sign the payload using the algorithm from the header and the key
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate serde_derive;
|
|
||||||
/// use jsonwebtoken::{encode, Algorithm, Header};
|
|
||||||
///
|
|
||||||
/// /// #[derive(Debug, Serialize, Deserialize)]
|
|
||||||
/// struct Claims {
|
|
||||||
/// sub: String,
|
|
||||||
/// company: String
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let my_claims = Claims {
|
|
||||||
/// sub: "b@b.com".to_owned(),
|
|
||||||
/// company: "ACME".to_owned()
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // my_claims is a struct that implements Serialize
|
|
||||||
/// // This will create a JWT using HS256 as algorithm
|
|
||||||
/// let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: &[u8]) -> Result<String> {
|
|
||||||
let encoded_header = to_jwt_part(&header)?;
|
|
||||||
let encoded_claims = to_jwt_part(&claims)?;
|
|
||||||
let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
|
|
||||||
let signature = sign(&*signing_input, key.as_ref(), header.alg)?;
|
|
||||||
|
|
||||||
Ok([signing_input, signature].join("."))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts
|
|
||||||
/// Errors if we don't
|
|
||||||
macro_rules! expect_two {
|
|
||||||
($iter:expr) => {{
|
|
||||||
let mut i = $iter;
|
|
||||||
match (i.next(), i.next(), i.next()) {
|
|
||||||
(Some(first), Some(second), None) => (first, second),
|
|
||||||
_ => return Err(ErrorKind::InvalidToken.into())
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a token into a struct containing 2 fields: `claims` and `header`.
|
|
||||||
///
|
|
||||||
/// If the token or its signature is invalid or the claims fail validation, it will return an error.
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate serde_derive;
|
|
||||||
/// use jsonwebtoken::{decode, Validation, Algorithm};
|
|
||||||
///
|
|
||||||
/// #[derive(Debug, Serialize, Deserialize)]
|
|
||||||
/// struct Claims {
|
|
||||||
/// sub: String,
|
|
||||||
/// company: String
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let token = "a.jwt.token".to_string();
|
|
||||||
/// // Claims is a struct that implements Deserialize
|
|
||||||
/// let token_data = decode::<Claims>(&token, "secret", &Validation::new(Algorithm::HS256));
|
|
||||||
/// ```
|
|
||||||
pub fn decode<T: DeserializeOwned>(token: &str, key: &[u8], validation: &Validation) -> Result<TokenData<T>> {
|
|
||||||
let (signature, signing_input) = expect_two!(token.rsplitn(2, '.'));
|
|
||||||
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.'));
|
|
||||||
let header: Header = from_jwt_part(header)?;
|
|
||||||
|
|
||||||
if !verify(signature, signing_input, key, header.alg)? {
|
|
||||||
return Err(ErrorKind::InvalidSignature.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validation.algorithms.contains(&header.alg) {
|
|
||||||
return Err(ErrorKind::InvalidAlgorithm.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?;
|
|
||||||
|
|
||||||
validate(&claims_map, validation)?;
|
|
||||||
|
|
||||||
Ok(TokenData { header: header, claims: decoded_claims })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be
|
|
||||||
/// used when you don't know which `alg` the token is using and want to find out.
|
|
||||||
///
|
|
||||||
/// If the token has an invalid format, it will return an error.
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// use jsonwebtoken::decode_header;
|
|
||||||
///
|
|
||||||
/// let token = "a.jwt.token".to_string();
|
|
||||||
/// let header = decode_header(&token);
|
|
||||||
/// ```
|
|
||||||
pub fn decode_header(token: &str) -> Result<Header> {
|
|
||||||
let (_, signing_input) = expect_two!(token.rsplitn(2, '.'));
|
|
||||||
let (_, header) = expect_two!(signing_input.rsplitn(2, '.'));
|
|
||||||
from_jwt_part(header)
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use base64;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
use serde_json::{from_str, to_string, Value};
|
|
||||||
use serde_json::map::Map;
|
|
||||||
|
|
||||||
use errors::{Result};
|
|
||||||
use header::Header;
|
|
||||||
|
|
||||||
|
|
||||||
/// The return type of a successful call to decode
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TokenData<T> {
|
|
||||||
/// The decoded JWT header
|
|
||||||
pub header: Header,
|
|
||||||
/// The decoded JWT claims
|
|
||||||
pub claims: T
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serializes to JSON and encodes to base64
|
|
||||||
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> {
|
|
||||||
let encoded = to_string(input)?;
|
|
||||||
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes from base64 and deserializes from JSON to a struct
|
|
||||||
pub fn from_jwt_part<B: AsRef<str>, T: DeserializeOwned>(encoded: B) -> Result<T> {
|
|
||||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
|
||||||
let s = String::from_utf8(decoded)?;
|
|
||||||
|
|
||||||
Ok(from_str(&s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap
|
|
||||||
pub fn from_jwt_part_claims<B: AsRef<str>, T: DeserializeOwned>(encoded: B) -> Result<(T, Map<String, Value>)> {
|
|
||||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
|
||||||
let s = String::from_utf8(decoded)?;
|
|
||||||
|
|
||||||
let claims: T = from_str(&s)?;
|
|
||||||
let map: Map<_,_> = from_str(&s)?;
|
|
||||||
Ok((claims, map))
|
|
||||||
}
|
|
|
@ -1,377 +0,0 @@
|
||||||
use chrono::Utc;
|
|
||||||
use serde::ser::Serialize;
|
|
||||||
use serde_json::{Value, from_value, to_value};
|
|
||||||
use serde_json::map::Map;
|
|
||||||
|
|
||||||
use errors::{Result, ErrorKind};
|
|
||||||
use crypto::Algorithm;
|
|
||||||
|
|
||||||
|
|
||||||
/// Contains the various validations that are applied after decoding a token.
|
|
||||||
///
|
|
||||||
/// All time validation happen on UTC timestamps.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use jsonwebtoken::Validation;
|
|
||||||
///
|
|
||||||
/// // Default value
|
|
||||||
/// let validation = Validation::default();
|
|
||||||
///
|
|
||||||
/// // Changing one parameter
|
|
||||||
/// let mut validation = Validation {leeway: 60, ..Default::default()};
|
|
||||||
///
|
|
||||||
/// // Setting audience
|
|
||||||
/// let mut validation = Validation::default();
|
|
||||||
/// validation.set_audience(&"Me"); // string
|
|
||||||
/// validation.set_audience(&["Me", "You"]); // array of strings
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Validation {
|
|
||||||
/// Add some leeway (in seconds) to the `exp`, `iat` and `nbf` validation to
|
|
||||||
/// account for clock skew.
|
|
||||||
///
|
|
||||||
/// Defaults to `0`.
|
|
||||||
pub leeway: i64,
|
|
||||||
/// Whether to validate the `exp` field.
|
|
||||||
///
|
|
||||||
/// It will return an error if the time in the `exp` field is past.
|
|
||||||
///
|
|
||||||
/// Defaults to `true`.
|
|
||||||
pub validate_exp: bool,
|
|
||||||
/// Whether to validate the `iat` field.
|
|
||||||
///
|
|
||||||
/// It will return an error if the time in the `iat` field is in the future.
|
|
||||||
///
|
|
||||||
/// Defaults to `true`.
|
|
||||||
pub validate_iat: bool,
|
|
||||||
/// Whether to validate the `nbf` field.
|
|
||||||
///
|
|
||||||
/// It will return an error if the current timestamp is before the time in the `nbf` field.
|
|
||||||
///
|
|
||||||
/// Defaults to `true`.
|
|
||||||
pub validate_nbf: bool,
|
|
||||||
/// If it contains a value, the validation will check that the `aud` field is the same as the
|
|
||||||
/// one provided and will error otherwise.
|
|
||||||
/// Since `aud` can be either a String or a Vec<String> in the JWT spec, you will need to use
|
|
||||||
/// the [set_audience](struct.Validation.html#method.set_audience) method to set it.
|
|
||||||
///
|
|
||||||
/// Defaults to `None`.
|
|
||||||
pub aud: Option<Value>,
|
|
||||||
/// If it contains a value, the validation will check that the `iss` field is the same as the
|
|
||||||
/// one provided and will error otherwise.
|
|
||||||
///
|
|
||||||
/// Defaults to `None`.
|
|
||||||
pub iss: Option<String>,
|
|
||||||
/// If it contains a value, the validation will check that the `sub` field is the same as the
|
|
||||||
/// one provided and will error otherwise.
|
|
||||||
///
|
|
||||||
/// Defaults to `None`.
|
|
||||||
pub sub: Option<String>,
|
|
||||||
/// If it contains a value, the validation will check that the `alg` of the header is contained
|
|
||||||
/// in the ones provided and will error otherwise.
|
|
||||||
///
|
|
||||||
/// Defaults to `vec![Algorithm::HS256]`.
|
|
||||||
pub algorithms: Vec<Algorithm>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validation {
|
|
||||||
/// Create a default validation setup allowing the given alg
|
|
||||||
pub fn new(alg: Algorithm) -> Validation {
|
|
||||||
let mut validation = Validation::default();
|
|
||||||
validation.algorithms = vec![alg];
|
|
||||||
validation
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Since `aud` can be either a String or an array of String in the JWT spec, this method will take
|
|
||||||
/// care of serializing the value.
|
|
||||||
pub fn set_audience<T: Serialize>(&mut self, audience: &T) {
|
|
||||||
self.aud = Some(to_value(audience).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Validation {
|
|
||||||
fn default() -> Validation {
|
|
||||||
Validation {
|
|
||||||
leeway: 0,
|
|
||||||
|
|
||||||
validate_exp: true,
|
|
||||||
validate_iat: true,
|
|
||||||
validate_nbf: true,
|
|
||||||
|
|
||||||
iss: None,
|
|
||||||
sub: None,
|
|
||||||
aud: None,
|
|
||||||
|
|
||||||
algorithms: vec![Algorithm::HS256],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()> {
|
|
||||||
let now = Utc::now().timestamp();
|
|
||||||
|
|
||||||
if let Some(iat) = claims.get("iat") {
|
|
||||||
if options.validate_iat && from_value::<i64>(iat.clone())? > now + options.leeway {
|
|
||||||
return Err(ErrorKind::InvalidIssuedAt.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(exp) = claims.get("exp") {
|
|
||||||
if options.validate_exp && from_value::<i64>(exp.clone())? < now - options.leeway {
|
|
||||||
return Err(ErrorKind::ExpiredSignature.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(nbf) = claims.get("nbf") {
|
|
||||||
if options.validate_nbf && from_value::<i64>(nbf.clone())? > now + options.leeway {
|
|
||||||
return Err(ErrorKind::ImmatureSignature.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(iss) = claims.get("iss") {
|
|
||||||
if let Some(ref correct_iss) = options.iss {
|
|
||||||
if from_value::<String>(iss.clone())? != *correct_iss {
|
|
||||||
return Err(ErrorKind::InvalidIssuer.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sub) = claims.get("sub") {
|
|
||||||
if let Some(ref correct_sub) = options.sub {
|
|
||||||
if from_value::<String>(sub.clone())? != *correct_sub {
|
|
||||||
return Err(ErrorKind::InvalidSubject.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(aud) = claims.get("aud") {
|
|
||||||
if let Some(ref correct_aud) = options.aud {
|
|
||||||
if aud != correct_aud {
|
|
||||||
return Err(ErrorKind::InvalidAudience.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde_json::{to_value};
|
|
||||||
use serde_json::map::Map;
|
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
use super::{validate, Validation};
|
|
||||||
|
|
||||||
use errors::ErrorKind;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iat_in_past_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("iat".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iat_in_future_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("iat".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::InvalidIssuedAt => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iat_in_future_but_in_leeway_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("iat".to_string(), to_value(Utc::now().timestamp() + 50).unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
leeway: 1000 * 60,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exp_in_future_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() + 10000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exp_in_past_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 100000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::ExpiredSignature => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exp_in_past_but_in_leeway_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("exp".to_string(), to_value(Utc::now().timestamp() - 500).unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
leeway: 1000 * 60,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nbf_in_past_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() - 10000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nbf_in_future_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 100000).unwrap());
|
|
||||||
let res = validate(&claims, &Validation::default());
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::ImmatureSignature => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nbf_in_future_but_in_leeway_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("nbf".to_string(), to_value(Utc::now().timestamp() + 500).unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
leeway: 1000 * 60,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iss_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("iss".to_string(), to_value("Keats").unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
iss: Some("Keats".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iss_not_matching_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("iss".to_string(), to_value("Hacked").unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
iss: Some("Keats".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::InvalidIssuer => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sub_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("sub".to_string(), to_value("Keats").unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
sub: Some("Keats".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sub_not_matching_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("sub".to_string(), to_value("Hacked").unwrap());
|
|
||||||
let validation = Validation {
|
|
||||||
sub: Some("Keats".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::InvalidSubject => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aud_string_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
|
||||||
let mut validation = Validation::default();
|
|
||||||
validation.set_audience(&"Everyone");
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aud_array_of_string_ok() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("aud".to_string(), to_value(["UserA", "UserB"]).unwrap());
|
|
||||||
let mut validation = Validation::default();
|
|
||||||
validation.set_audience(&["UserA", "UserB"]);
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aud_type_mismatch_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
|
||||||
let mut validation = Validation::default();
|
|
||||||
validation.set_audience(&["UserA", "UserB"]);
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::InvalidAudience => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aud_correct_type_not_matching_fails() {
|
|
||||||
let mut claims = Map::new();
|
|
||||||
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
|
||||||
let mut validation = Validation::default();
|
|
||||||
validation.set_audience(&"None");
|
|
||||||
let res = validate(&claims, &validation);
|
|
||||||
assert!(res.is_err());
|
|
||||||
|
|
||||||
match res.unwrap_err().kind() {
|
|
||||||
&ErrorKind::InvalidAudience => (),
|
|
||||||
_ => assert!(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +1 @@
|
||||||
nightly-2018-10-03
|
nightly-2018-10-31
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use rocket_contrib::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
@ -9,6 +9,29 @@ use mail;
|
||||||
|
|
||||||
use CONFIG;
|
use CONFIG;
|
||||||
|
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
register,
|
||||||
|
profile,
|
||||||
|
put_profile,
|
||||||
|
post_profile,
|
||||||
|
get_public_keys,
|
||||||
|
post_keys,
|
||||||
|
post_password,
|
||||||
|
post_kdf,
|
||||||
|
post_sstamp,
|
||||||
|
post_email_token,
|
||||||
|
post_email,
|
||||||
|
delete_account,
|
||||||
|
post_delete_account,
|
||||||
|
revision_date,
|
||||||
|
password_hint,
|
||||||
|
prelogin,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct RegisterData {
|
struct RegisterData {
|
||||||
|
|
|
@ -1,35 +1,75 @@
|
||||||
use std::path::Path;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use rocket::State;
|
|
||||||
use rocket::Data;
|
|
||||||
use rocket::http::ContentType;
|
use rocket::http::ContentType;
|
||||||
|
use rocket::{request::Form, Data, Route, State};
|
||||||
|
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use multipart::server::{Multipart, SaveResult};
|
|
||||||
use multipart::server::save::SavedData;
|
use multipart::server::save::SavedData;
|
||||||
|
use multipart::server::{Multipart, SaveResult};
|
||||||
|
|
||||||
use data_encoding::HEXLOWER;
|
use data_encoding::HEXLOWER;
|
||||||
|
|
||||||
use db::DbConn;
|
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
use db::DbConn;
|
||||||
|
|
||||||
use crypto;
|
use crypto;
|
||||||
|
|
||||||
use api::{self, PasswordData, JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
|
use api::{self, EmptyResult, JsonResult, JsonUpcase, PasswordData, UpdateType, WebSocketUsers};
|
||||||
use auth::Headers;
|
use auth::Headers;
|
||||||
|
|
||||||
use CONFIG;
|
use CONFIG;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
pub fn routes() -> Vec<Route> {
|
||||||
#[allow(non_snake_case)]
|
routes![
|
||||||
struct SyncData {
|
sync,
|
||||||
excludeDomains: bool,
|
get_ciphers,
|
||||||
|
get_cipher,
|
||||||
|
get_cipher_admin,
|
||||||
|
get_cipher_details,
|
||||||
|
post_ciphers,
|
||||||
|
put_cipher_admin,
|
||||||
|
post_ciphers_admin,
|
||||||
|
post_ciphers_import,
|
||||||
|
post_attachment,
|
||||||
|
post_attachment_admin,
|
||||||
|
post_attachment_share,
|
||||||
|
delete_attachment_post,
|
||||||
|
delete_attachment_post_admin,
|
||||||
|
delete_attachment,
|
||||||
|
delete_attachment_admin,
|
||||||
|
post_cipher_admin,
|
||||||
|
post_cipher_share,
|
||||||
|
put_cipher_share,
|
||||||
|
put_cipher_share_seleted,
|
||||||
|
post_cipher,
|
||||||
|
put_cipher,
|
||||||
|
delete_cipher_post,
|
||||||
|
delete_cipher_post_admin,
|
||||||
|
delete_cipher,
|
||||||
|
delete_cipher_admin,
|
||||||
|
delete_cipher_selected,
|
||||||
|
delete_cipher_selected_post,
|
||||||
|
delete_all,
|
||||||
|
move_cipher_selected,
|
||||||
|
move_cipher_selected_put,
|
||||||
|
|
||||||
|
post_collections_update,
|
||||||
|
post_collections_admin,
|
||||||
|
put_collections_admin,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/sync?<data>")]
|
#[derive(FromForm, Default)]
|
||||||
fn sync(data: SyncData, headers: Headers, conn: DbConn) -> JsonResult {
|
struct SyncData {
|
||||||
|
#[form(field = "excludeDomains")]
|
||||||
|
exclude_domains: bool, // Default: 'false'
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/sync?<data..>")]
|
||||||
|
fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let user_json = headers.user.to_json(&conn);
|
let user_json = headers.user.to_json(&conn);
|
||||||
|
|
||||||
let folders = Folder::find_by_user(&headers.user.uuid, &conn);
|
let folders = Folder::find_by_user(&headers.user.uuid, &conn);
|
||||||
|
@ -41,7 +81,7 @@ fn sync(data: SyncData, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
|
let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
|
||||||
let ciphers_json: Vec<Value> = ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
let ciphers_json: Vec<Value> = ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
||||||
|
|
||||||
let domains_json = if data.excludeDomains { Value::Null } else { api::core::get_eq_domains(headers).unwrap().into_inner() };
|
let domains_json = if data.exclude_domains { Value::Null } else { api::core::get_eq_domains(headers).unwrap().into_inner() };
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"Profile": user_json,
|
"Profile": user_json,
|
||||||
|
@ -53,14 +93,6 @@ fn sync(data: SyncData, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/sync")]
|
|
||||||
fn sync_no_query(headers: Headers, conn: DbConn) -> JsonResult {
|
|
||||||
let sync_data = SyncData {
|
|
||||||
excludeDomains: false,
|
|
||||||
};
|
|
||||||
sync(sync_data, headers, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/ciphers")]
|
#[get("/ciphers")]
|
||||||
fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult {
|
fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
|
let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
|
||||||
|
@ -695,8 +727,7 @@ fn delete_all(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn, ws
|
||||||
for f in Folder::find_by_user(&user.uuid, &conn) {
|
for f in Folder::find_by_user(&user.uuid, &conn) {
|
||||||
if f.delete(&conn).is_err() {
|
if f.delete(&conn).is_err() {
|
||||||
err!("Failed deleting folder")
|
err!("Failed deleting folder")
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ws.send_folder_update(UpdateType::SyncFolderCreate, &f);
|
ws.send_folder_update(UpdateType::SyncFolderCreate, &f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
@ -7,6 +8,20 @@ use db::models::*;
|
||||||
use api::{JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
|
use api::{JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
|
||||||
use auth::Headers;
|
use auth::Headers;
|
||||||
|
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
get_folders,
|
||||||
|
get_folder,
|
||||||
|
post_folders,
|
||||||
|
post_folder,
|
||||||
|
put_folder,
|
||||||
|
delete_folder_post,
|
||||||
|
delete_folder,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/folders")]
|
#[get("/folders")]
|
||||||
fn get_folders(headers: Headers, conn: DbConn) -> JsonResult {
|
fn get_folders(headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let folders = Folder::find_by_user(&headers.user.uuid, &conn);
|
let folders = Folder::find_by_user(&headers.user.uuid, &conn);
|
||||||
|
|
|
@ -4,126 +4,25 @@ mod folders;
|
||||||
mod organizations;
|
mod organizations;
|
||||||
pub(crate) mod two_factor;
|
pub(crate) mod two_factor;
|
||||||
|
|
||||||
use self::accounts::*;
|
|
||||||
use self::ciphers::*;
|
|
||||||
use self::folders::*;
|
|
||||||
use self::organizations::*;
|
|
||||||
use self::two_factor::*;
|
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
let mut mod_routes = routes![
|
||||||
register,
|
|
||||||
profile,
|
|
||||||
put_profile,
|
|
||||||
post_profile,
|
|
||||||
get_public_keys,
|
|
||||||
post_keys,
|
|
||||||
post_password,
|
|
||||||
post_kdf,
|
|
||||||
post_sstamp,
|
|
||||||
post_email_token,
|
|
||||||
post_email,
|
|
||||||
delete_account,
|
|
||||||
post_delete_account,
|
|
||||||
revision_date,
|
|
||||||
password_hint,
|
|
||||||
prelogin,
|
|
||||||
|
|
||||||
sync,
|
|
||||||
sync_no_query,
|
|
||||||
|
|
||||||
get_ciphers,
|
|
||||||
get_cipher,
|
|
||||||
get_cipher_admin,
|
|
||||||
get_cipher_details,
|
|
||||||
post_ciphers,
|
|
||||||
put_cipher_admin,
|
|
||||||
post_ciphers_admin,
|
|
||||||
post_ciphers_import,
|
|
||||||
post_attachment,
|
|
||||||
post_attachment_admin,
|
|
||||||
post_attachment_share,
|
|
||||||
delete_attachment_post,
|
|
||||||
delete_attachment_post_admin,
|
|
||||||
delete_attachment,
|
|
||||||
delete_attachment_admin,
|
|
||||||
post_cipher_admin,
|
|
||||||
post_cipher_share,
|
|
||||||
put_cipher_share,
|
|
||||||
put_cipher_share_seleted,
|
|
||||||
post_cipher,
|
|
||||||
put_cipher,
|
|
||||||
delete_cipher_post,
|
|
||||||
delete_cipher_post_admin,
|
|
||||||
delete_cipher,
|
|
||||||
delete_cipher_admin,
|
|
||||||
delete_cipher_selected,
|
|
||||||
delete_cipher_selected_post,
|
|
||||||
delete_all,
|
|
||||||
move_cipher_selected,
|
|
||||||
move_cipher_selected_put,
|
|
||||||
|
|
||||||
get_folders,
|
|
||||||
get_folder,
|
|
||||||
post_folders,
|
|
||||||
post_folder,
|
|
||||||
put_folder,
|
|
||||||
delete_folder_post,
|
|
||||||
delete_folder,
|
|
||||||
|
|
||||||
get_twofactor,
|
|
||||||
get_recover,
|
|
||||||
recover,
|
|
||||||
disable_twofactor,
|
|
||||||
disable_twofactor_put,
|
|
||||||
generate_authenticator,
|
|
||||||
activate_authenticator,
|
|
||||||
activate_authenticator_put,
|
|
||||||
generate_u2f,
|
|
||||||
activate_u2f,
|
|
||||||
activate_u2f_put,
|
|
||||||
|
|
||||||
get_organization,
|
|
||||||
create_organization,
|
|
||||||
delete_organization,
|
|
||||||
post_delete_organization,
|
|
||||||
leave_organization,
|
|
||||||
get_user_collections,
|
|
||||||
get_org_collections,
|
|
||||||
get_org_collection_detail,
|
|
||||||
get_collection_users,
|
|
||||||
put_organization,
|
|
||||||
post_organization,
|
|
||||||
post_organization_collections,
|
|
||||||
delete_organization_collection_user,
|
|
||||||
post_organization_collection_delete_user,
|
|
||||||
post_organization_collection_update,
|
|
||||||
put_organization_collection_update,
|
|
||||||
delete_organization_collection,
|
|
||||||
post_organization_collection_delete,
|
|
||||||
post_collections_update,
|
|
||||||
post_collections_admin,
|
|
||||||
put_collections_admin,
|
|
||||||
get_org_details,
|
|
||||||
get_org_users,
|
|
||||||
send_invite,
|
|
||||||
confirm_invite,
|
|
||||||
get_user,
|
|
||||||
edit_user,
|
|
||||||
put_organization_user,
|
|
||||||
delete_user,
|
|
||||||
post_delete_user,
|
|
||||||
post_reinvite_user,
|
|
||||||
post_org_import,
|
|
||||||
|
|
||||||
clear_device_token,
|
clear_device_token,
|
||||||
put_device_token,
|
put_device_token,
|
||||||
|
|
||||||
get_eq_domains,
|
get_eq_domains,
|
||||||
post_eq_domains,
|
post_eq_domains,
|
||||||
put_eq_domains,
|
put_eq_domains,
|
||||||
|
];
|
||||||
|
|
||||||
]
|
let mut routes = Vec::new();
|
||||||
|
routes.append(&mut accounts::routes());
|
||||||
|
routes.append(&mut ciphers::routes());
|
||||||
|
routes.append(&mut folders::routes());
|
||||||
|
routes.append(&mut organizations::routes());
|
||||||
|
routes.append(&mut two_factor::routes());
|
||||||
|
routes.append(&mut mod_routes);
|
||||||
|
|
||||||
|
routes
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -132,7 +31,8 @@ pub fn routes() -> Vec<Route> {
|
||||||
|
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
@ -141,8 +41,8 @@ use api::{JsonResult, EmptyResult, JsonUpcase};
|
||||||
use auth::Headers;
|
use auth::Headers;
|
||||||
|
|
||||||
#[put("/devices/identifier/<uuid>/clear-token", data = "<data>")]
|
#[put("/devices/identifier/<uuid>/clear-token", data = "<data>")]
|
||||||
fn clear_device_token(uuid: String, data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
|
fn clear_device_token(uuid: String, data: JsonUpcase<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
let _data: Value = data.into_inner();
|
let _data: Value = data.into_inner().data;
|
||||||
|
|
||||||
let device = match Device::find_by_uuid(&uuid, &conn) {
|
let device = match Device::find_by_uuid(&uuid, &conn) {
|
||||||
Some(device) => device,
|
Some(device) => device,
|
||||||
|
@ -160,8 +60,8 @@ fn clear_device_token(uuid: String, data: Json<Value>, headers: Headers, conn: D
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||||
fn put_device_token(uuid: String, data: Json<Value>, headers: Headers, conn: DbConn) -> JsonResult {
|
fn put_device_token(uuid: String, data: JsonUpcase<Value>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let _data: Value = data.into_inner();
|
let _data: Value = data.into_inner().data;
|
||||||
|
|
||||||
let device = match Device::find_by_uuid(&uuid, &conn) {
|
let device = match Device::find_by_uuid(&uuid, &conn) {
|
||||||
Some(device) => device,
|
Some(device) => device,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket::request::Form;
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use CONFIG;
|
use CONFIG;
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
@ -9,6 +12,42 @@ use auth::{Headers, AdminHeaders, OwnerHeaders};
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
get_organization,
|
||||||
|
create_organization,
|
||||||
|
delete_organization,
|
||||||
|
post_delete_organization,
|
||||||
|
leave_organization,
|
||||||
|
get_user_collections,
|
||||||
|
get_org_collections,
|
||||||
|
get_org_collection_detail,
|
||||||
|
get_collection_users,
|
||||||
|
put_organization,
|
||||||
|
post_organization,
|
||||||
|
post_organization_collections,
|
||||||
|
delete_organization_collection_user,
|
||||||
|
post_organization_collection_delete_user,
|
||||||
|
post_organization_collection_update,
|
||||||
|
put_organization_collection_update,
|
||||||
|
delete_organization_collection,
|
||||||
|
post_organization_collection_delete,
|
||||||
|
get_org_details,
|
||||||
|
get_org_users,
|
||||||
|
send_invite,
|
||||||
|
confirm_invite,
|
||||||
|
get_user,
|
||||||
|
edit_user,
|
||||||
|
put_organization_user,
|
||||||
|
delete_user,
|
||||||
|
post_delete_user,
|
||||||
|
post_reinvite_user,
|
||||||
|
post_org_import,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -315,14 +354,14 @@ fn get_collection_users(org_id: String, coll_id: String, _headers: AdminHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
#[allow(non_snake_case)]
|
|
||||||
struct OrgIdData {
|
struct OrgIdData {
|
||||||
organizationId: String
|
#[form(field = "organizationId")]
|
||||||
|
organization_id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ciphers/organization-details?<data>")]
|
#[get("/ciphers/organization-details?<data..>")]
|
||||||
fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult {
|
fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let ciphers = Cipher::find_by_org(&data.organizationId, &conn);
|
let ciphers = Cipher::find_by_org(&data.organization_id, &conn);
|
||||||
let ciphers_json: Vec<Value> = ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
let ciphers_json: Vec<Value> = ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
|
@ -643,10 +682,10 @@ struct RelationsData {
|
||||||
Value: usize,
|
Value: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/import-organization?<query>", data = "<data>")]
|
#[post("/ciphers/import-organization?<query..>", data = "<data>")]
|
||||||
fn post_org_import(query: OrgIdData, data: JsonUpcase<ImportData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
|
fn post_org_import(query: Form<OrgIdData>, data: JsonUpcase<ImportData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
|
||||||
let data: ImportData = data.into_inner().data;
|
let data: ImportData = data.into_inner().data;
|
||||||
let org_id = query.organizationId;
|
let org_id = query.into_inner().organization_id;
|
||||||
|
|
||||||
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
|
@ -700,4 +739,4 @@ fn post_org_import(query: OrgIdData, data: JsonUpcase<ImportData>, headers: Head
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
Err(_) => err!("Failed to update the revision, please log out and log back in to finish import.")
|
Err(_) => err!("Failed to update the revision, please log out and log back in to finish import.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use data_encoding::BASE32;
|
use data_encoding::BASE32;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
||||||
use db::{
|
use db::{
|
||||||
models::{TwoFactor, TwoFactorType, User},
|
models::{TwoFactor, TwoFactorType, User},
|
||||||
|
@ -12,6 +14,24 @@ use crypto;
|
||||||
use api::{ApiResult, JsonResult, JsonUpcase, NumberOrString, PasswordData};
|
use api::{ApiResult, JsonResult, JsonUpcase, NumberOrString, PasswordData};
|
||||||
use auth::Headers;
|
use auth::Headers;
|
||||||
|
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
get_twofactor,
|
||||||
|
get_recover,
|
||||||
|
recover,
|
||||||
|
disable_twofactor,
|
||||||
|
disable_twofactor_put,
|
||||||
|
generate_authenticator,
|
||||||
|
activate_authenticator,
|
||||||
|
activate_authenticator_put,
|
||||||
|
generate_u2f,
|
||||||
|
activate_u2f,
|
||||||
|
activate_u2f_put,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/two-factor")]
|
#[get("/two-factor")]
|
||||||
fn get_twofactor(headers: Headers, conn: DbConn) -> JsonResult {
|
fn get_twofactor(headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn);
|
let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn);
|
||||||
|
|
|
@ -4,7 +4,8 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use rocket::request::{self, Form, FormItems, FromForm, FromRequest, Request};
|
use rocket::request::{self, Form, FormItems, FromForm, FromRequest, Request};
|
||||||
use rocket::{Outcome, Route};
|
use rocket::{Outcome, Route};
|
||||||
|
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
|
||||||
|
@ -21,9 +22,9 @@ pub fn routes() -> Vec<Route> {
|
||||||
routes![login]
|
routes![login]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/connect/token", data = "<connect_data>")]
|
#[post("/connect/token", data = "<data>")]
|
||||||
fn login(connect_data: Form<ConnectData>, device_type: DeviceType, conn: DbConn, socket: Option<SocketAddr>) -> JsonResult {
|
fn login(data: Form<ConnectData>, device_type: DeviceType, conn: DbConn, socket: Option<SocketAddr>) -> JsonResult {
|
||||||
let data = connect_data.get();
|
let data: ConnectData = data.into_inner();
|
||||||
|
|
||||||
match data.grant_type {
|
match data.grant_type {
|
||||||
GrantType::RefreshToken => _refresh_login(data, device_type, conn),
|
GrantType::RefreshToken => _refresh_login(data, device_type, conn),
|
||||||
|
@ -31,7 +32,7 @@ fn login(connect_data: Form<ConnectData>, device_type: DeviceType, conn: DbConn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _refresh_login(data: &ConnectData, _device_type: DeviceType, conn: DbConn) -> JsonResult {
|
fn _refresh_login(data: ConnectData, _device_type: DeviceType, conn: DbConn) -> JsonResult {
|
||||||
// Extract token
|
// Extract token
|
||||||
let token = data.get("refresh_token");
|
let token = data.get("refresh_token");
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ fn _refresh_login(data: &ConnectData, _device_type: DeviceType, conn: DbConn) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _password_login(data: &ConnectData, device_type: DeviceType, conn: DbConn, remote: Option<SocketAddr>) -> JsonResult {
|
fn _password_login(data: ConnectData, device_type: DeviceType, conn: DbConn, remote: Option<SocketAddr>) -> JsonResult {
|
||||||
// Get the ip for error reporting
|
// Get the ip for error reporting
|
||||||
let ip = match remote {
|
let ip = match remote {
|
||||||
Some(ip) => ip.ip(),
|
Some(ip) => ip.ip(),
|
||||||
|
@ -319,11 +320,9 @@ impl<'f> FromForm<'f> for ConnectData {
|
||||||
let mut data = HashMap::new();
|
let mut data = HashMap::new();
|
||||||
|
|
||||||
// Insert data into map
|
// Insert data into map
|
||||||
for (key, value) in items {
|
for item in items {
|
||||||
match (key.url_decode(), value.url_decode()) {
|
let (key, value) = item.key_value_decoded();
|
||||||
(Ok(key), Ok(value)) => data.insert(key.to_lowercase(), value),
|
data.insert(key.to_lowercase(), value);
|
||||||
_ => return Err("Error decoding key or value".to_string()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate needed values
|
// Validate needed values
|
||||||
|
|
|
@ -12,11 +12,12 @@ pub use self::notifications::routes as notifications_routes;
|
||||||
pub use self::notifications::{start_notification_server, WebSocketUsers, UpdateType};
|
pub use self::notifications::{start_notification_server, WebSocketUsers, UpdateType};
|
||||||
|
|
||||||
use rocket::response::status::BadRequest;
|
use rocket::response::status::BadRequest;
|
||||||
use rocket_contrib::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
// Type aliases for API methods results
|
// Type aliases for API methods results
|
||||||
type ApiResult<T> = Result<T, BadRequest<Json>>;
|
type ApiResult<T> = Result<T, BadRequest<Json<Value>>>;
|
||||||
type JsonResult = ApiResult<Json>;
|
type JsonResult = ApiResult<Json<Value>>;
|
||||||
type EmptyResult = ApiResult<()>;
|
type EmptyResult = ApiResult<()>;
|
||||||
|
|
||||||
use util;
|
use util;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
use rocket_contrib::Json;
|
use rocket_contrib::json::Json;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use api::JsonResult;
|
use api::JsonResult;
|
||||||
|
|
|
@ -6,7 +6,8 @@ use rocket::response::{self, NamedFile, Responder};
|
||||||
use rocket::response::content::Content;
|
use rocket::response::content::Content;
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::json::Json;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use CONFIG;
|
use CONFIG;
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub fn decode_jwt(token: &str) -> Result<JWTClaims, String> {
|
||||||
let validation = jwt::Validation {
|
let validation = jwt::Validation {
|
||||||
leeway: 30, // 30 seconds
|
leeway: 30, // 30 seconds
|
||||||
validate_exp: true,
|
validate_exp: true,
|
||||||
validate_iat: true,
|
validate_iat: false, // IssuedAt is the same as NotBefore
|
||||||
validate_nbf: true,
|
validate_nbf: true,
|
||||||
aud: None,
|
aud: None,
|
||||||
iss: Some(JWT_ISSUER.clone()),
|
iss: Some(JWT_ISSUER.clone()),
|
||||||
|
@ -197,8 +197,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
|
||||||
Outcome::Success(headers) => {
|
Outcome::Success(headers) => {
|
||||||
// org_id is expected to be the first dynamic param
|
// org_id is expected to be the first dynamic param
|
||||||
match request.get_param::<String>(0) {
|
match request.get_param::<String>(0) {
|
||||||
Err(_) => err_handler!("Error getting the organization id"),
|
Some(Ok(org_id)) => {
|
||||||
Ok(org_id) => {
|
|
||||||
let conn = match request.guard::<DbConn>() {
|
let conn = match request.guard::<DbConn>() {
|
||||||
Outcome::Success(conn) => conn,
|
Outcome::Success(conn) => conn,
|
||||||
_ => err_handler!("Error getting DB")
|
_ => err_handler!("Error getting DB")
|
||||||
|
@ -227,7 +226,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
|
||||||
user: headers.user,
|
user: headers.user,
|
||||||
org_user_type: org_user.type_,
|
org_user_type: org_user.type_,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
_ => err_handler!("Error getting the organization id"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::Cipher;
|
use super::Cipher;
|
||||||
use CONFIG;
|
use CONFIG;
|
||||||
|
@ -29,7 +29,7 @@ impl Attachment {
|
||||||
format!("{}/{}/{}", CONFIG.attachments_folder, self.cipher_uuid, self.id)
|
format!("{}/{}/{}", CONFIG.attachments_folder, self.cipher_uuid, self.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self, host: &str) -> JsonValue {
|
pub fn to_json(&self, host: &str) -> Value {
|
||||||
use util::get_display_size;
|
use util::get_display_size;
|
||||||
|
|
||||||
let web_path = format!("{}/attachments/{}/{}", host, self.cipher_uuid, self.id);
|
let web_path = format!("{}/attachments/{}/{}", host, self.cipher_uuid, self.id);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -68,23 +68,23 @@ use db::schema::*;
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl Cipher {
|
impl Cipher {
|
||||||
pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> JsonValue {
|
pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> Value {
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use util::format_date;
|
use util::format_date;
|
||||||
use super::Attachment;
|
use super::Attachment;
|
||||||
|
|
||||||
let attachments = Attachment::find_by_cipher(&self.uuid, conn);
|
let attachments = Attachment::find_by_cipher(&self.uuid, conn);
|
||||||
let attachments_json: Vec<JsonValue> = attachments.iter().map(|c| c.to_json(host)).collect();
|
let attachments_json: Vec<Value> = attachments.iter().map(|c| c.to_json(host)).collect();
|
||||||
|
|
||||||
let fields_json: JsonValue = if let Some(ref fields) = self.fields {
|
let fields_json: Value = if let Some(ref fields) = self.fields {
|
||||||
serde_json::from_str(fields).unwrap()
|
serde_json::from_str(fields).unwrap()
|
||||||
} else { JsonValue::Null };
|
} else { Value::Null };
|
||||||
|
|
||||||
let password_history_json: JsonValue = if let Some(ref password_history) = self.password_history {
|
let password_history_json: Value = if let Some(ref password_history) = self.password_history {
|
||||||
serde_json::from_str(password_history).unwrap()
|
serde_json::from_str(password_history).unwrap()
|
||||||
} else { JsonValue::Null };
|
} else { Value::Null };
|
||||||
|
|
||||||
let mut data_json: JsonValue = serde_json::from_str(&self.data).unwrap();
|
let mut data_json: Value = serde_json::from_str(&self.data).unwrap();
|
||||||
|
|
||||||
// TODO: ******* Backwards compat start **********
|
// TODO: ******* Backwards compat start **********
|
||||||
// To remove backwards compatibility, just remove this entire section
|
// To remove backwards compatibility, just remove this entire section
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> JsonValue {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"OrganizationId": self.org_uuid,
|
"OrganizationId": self.org_uuid,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ impl Folder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> JsonValue {
|
pub fn to_json(&self) -> Value {
|
||||||
use util::format_date;
|
use util::format_date;
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use super::{User, CollectionUser, Invitation};
|
use super::{User, CollectionUser, Invitation};
|
||||||
|
@ -70,7 +70,7 @@ impl Organization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> JsonValue {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Id": self.uuid,
|
"Id": self.uuid,
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
|
@ -181,7 +181,7 @@ impl Organization {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserOrganization {
|
impl UserOrganization {
|
||||||
pub fn to_json(&self, conn: &DbConn) -> JsonValue {
|
pub fn to_json(&self, conn: &DbConn) -> Value {
|
||||||
let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap();
|
let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap();
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
|
@ -209,7 +209,7 @@ impl UserOrganization {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json_user_details(&self, conn: &DbConn) -> JsonValue {
|
pub fn to_json_user_details(&self, conn: &DbConn) -> Value {
|
||||||
let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
|
let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
|
@ -226,7 +226,7 @@ impl UserOrganization {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json_collection_user_details(&self, read_only: bool, conn: &DbConn) -> JsonValue {
|
pub fn to_json_collection_user_details(&self, read_only: bool, conn: &DbConn) -> Value {
|
||||||
let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
|
let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
|
@ -241,7 +241,7 @@ impl UserOrganization {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json_details(&self, conn: &DbConn) -> JsonValue {
|
pub fn to_json_details(&self, conn: &DbConn) -> Value {
|
||||||
let coll_uuids = if self.access_all {
|
let coll_uuids = if self.access_all {
|
||||||
vec![] // If we have complete access, no need to fill the array
|
vec![] // If we have complete access, no need to fill the array
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ impl TwoFactor {
|
||||||
generated == totp_code
|
generated == totp_code
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> JsonValue {
|
pub fn to_json(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Enabled": self.enabled,
|
"Enabled": self.enabled,
|
||||||
"Key": "", // This key and value vary
|
"Key": "", // This key and value vary
|
||||||
|
@ -67,7 +67,7 @@ impl TwoFactor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json_list(&self) -> JsonValue {
|
pub fn to_json_list(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"Enabled": self.enabled,
|
"Enabled": self.enabled,
|
||||||
"Type": self.type_,
|
"Type": self.type_,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -120,14 +120,14 @@ use super::{Cipher, Folder, Device, UserOrganization, UserOrgType};
|
||||||
|
|
||||||
/// Database methods
|
/// Database methods
|
||||||
impl User {
|
impl User {
|
||||||
pub fn to_json(&self, conn: &DbConn) -> JsonValue {
|
pub fn to_json(&self, conn: &DbConn) -> Value {
|
||||||
use super::{UserOrganization, UserOrgType, UserOrgStatus, TwoFactor};
|
use super::{UserOrganization, UserOrgType, UserOrgStatus, TwoFactor};
|
||||||
|
|
||||||
let mut orgs = UserOrganization::find_by_user(&self.uuid, conn);
|
let mut orgs = UserOrganization::find_by_user(&self.uuid, conn);
|
||||||
if self.is_server_admin() {
|
if self.is_server_admin() {
|
||||||
orgs.push(UserOrganization::new_virtual(self.uuid.clone(), UserOrgType::Owner, UserOrgStatus::Confirmed));
|
orgs.push(UserOrganization::new_virtual(self.uuid.clone(), UserOrgType::Owner, UserOrgStatus::Confirmed));
|
||||||
}
|
}
|
||||||
let orgs_json: Vec<JsonValue> = orgs.iter().map(|c| c.to_json(&conn)).collect();
|
let orgs_json: Vec<Value> = orgs.iter().map(|c| c.to_json(&conn)).collect();
|
||||||
let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).is_empty();
|
let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).is_empty();
|
||||||
|
|
||||||
json!({
|
json!({
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#![feature(plugin, custom_derive, vec_remove_item, try_trait)]
|
#![feature(proc_macro_hygiene, decl_macro, custom_derive, vec_remove_item, try_trait)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![recursion_limit="128"]
|
#![recursion_limit="128"]
|
||||||
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
|
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
|
|
|
@ -23,7 +23,7 @@ macro_rules! err {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err_json {
|
macro_rules! err_json {
|
||||||
($expr:expr) => {{
|
($expr:expr) => {{
|
||||||
return Err($crate::rocket::response::status::BadRequest(Some($crate::rocket_contrib::Json($expr))));
|
return Err($crate::rocket::response::status::BadRequest(Some($crate::rocket_contrib::json::Json($expr))));
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Laden …
In neuem Issue referenzieren