mirror of https://github.com/rwf2/Rocket.git
Remove 'Config::profile()'. CFG 'secret_key' field.
This commit makes the `Config.secret_key` conditionally compile on the `secrets` feature. The net effect is simplified internal code, fewer corner-cases, and easier to write tests. This commit removes the `Provider::profile()` implementation of `Config`. This means that the `Config` provider no longer sets a profile, a likely confusing behavior. The `Config::figment()` continues to function as before.
This commit is contained in:
parent
f454895023
commit
83ffe0f7bc
|
@ -52,9 +52,5 @@ optional = true
|
|||
default-features = false
|
||||
features = ["std"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
rocket = { version = "0.5.0-dev", path = "../lib" }
|
||||
|
||||
[build-dependencies]
|
||||
version_check = "0.9"
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
fn main() {
|
||||
if let Some(true) = version_check::is_feature_flaggable() {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
}
|
|
@ -627,6 +627,18 @@ impl<'h> HeaderMap<'h> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<cookie::Cookie<'_>> for Header<'static> {
|
||||
fn from(cookie: cookie::Cookie<'_>) -> Header<'static> {
|
||||
Header::new("Set-Cookie", cookie.encoded().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&cookie::Cookie<'_>> for Header<'static> {
|
||||
fn from(cookie: &cookie::Cookie<'_>) -> Header<'static> {
|
||||
Header::new("Set-Cookie", cookie.encoded().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HeaderMap;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#![recursion_limit="512"]
|
||||
|
||||
#![cfg_attr(nightly, feature(doc_cfg))]
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
|
@ -23,13 +21,8 @@ pub mod ext;
|
|||
#[macro_use]
|
||||
mod docify;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "tls")]
|
||||
pub mod tls;
|
||||
|
||||
#[macro_use]
|
||||
mod header;
|
||||
mod cookies;
|
||||
mod method;
|
||||
mod status;
|
||||
mod raw_str;
|
||||
|
@ -45,22 +38,20 @@ pub mod uncased {
|
|||
#[doc(inline)] pub use uncased::*;
|
||||
}
|
||||
|
||||
// Types that we expose for use by core.
|
||||
// Types that we expose for use _only_ by core. Please don't use this.
|
||||
#[doc(hidden)]
|
||||
#[path = "."]
|
||||
pub mod private {
|
||||
#[cfg(feature = "tls")]
|
||||
pub mod tls;
|
||||
|
||||
pub use crate::parse::Indexed;
|
||||
pub use smallvec::{SmallVec, Array};
|
||||
|
||||
pub mod cookie {
|
||||
pub use cookie::*;
|
||||
pub use crate::cookies::Key;
|
||||
}
|
||||
|
||||
pub use crate::listener::{Incoming, Listener, Connection, bind_tcp};
|
||||
pub use cookie;
|
||||
}
|
||||
|
||||
pub use crate::method::Method;
|
||||
pub use crate::status::{Status, StatusClass};
|
||||
pub use crate::raw_str::RawStr;
|
||||
pub use crate::cookies::{Cookie, CookieJar, SameSite};
|
||||
pub use crate::header::*;
|
||||
|
|
|
@ -7,10 +7,13 @@ use figment::value::{Map, Dict};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use yansi::Paint;
|
||||
|
||||
use crate::config::{SecretKey, TlsConfig, LogLevel};
|
||||
use crate::config::{TlsConfig, LogLevel};
|
||||
use crate::request::{self, Request, FromRequest};
|
||||
use crate::data::Limits;
|
||||
|
||||
#[cfg(feature = "secrets")]
|
||||
use crate::config::SecretKey;
|
||||
|
||||
/// Rocket server configuration.
|
||||
///
|
||||
/// See the [module level docs](crate::config) as well as the [configuration
|
||||
|
@ -35,13 +38,7 @@ use crate::data::Limits;
|
|||
///
|
||||
/// * **Profile**
|
||||
///
|
||||
/// The selected profile is the value of the `ROCKET_PROFILE` environment
|
||||
/// variable. If the environment variable is not set, the profile is
|
||||
/// selected based on whether compilation is in debug mode, where "debug" is
|
||||
/// selected, or release mode, where "release" is selected.
|
||||
/// [`Config::DEBUG_PROFILE`] and [`Config::RELEASE_PROFILE`] encode these
|
||||
/// values as constants, while [`Config::DEFAULT_PROFILE`] selects the
|
||||
/// appropriate of the two at compile-time.
|
||||
/// This provider does not set a profile.
|
||||
///
|
||||
/// * **Metadata**
|
||||
///
|
||||
|
@ -53,6 +50,8 @@ use crate::data::Limits;
|
|||
/// The data emitted by this provider are the keys and values corresponding
|
||||
/// to the fields and values of the structure. The dictionary is emitted to
|
||||
/// the "default" meta-profile.
|
||||
///
|
||||
/// Note that these behaviors differ from those of [`Config::figment()`].
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
/// IP address to serve on. **(default: `127.0.0.1`)**
|
||||
|
@ -68,6 +67,12 @@ pub struct Config {
|
|||
/// The TLS configuration, if any. **(default: `None`)**
|
||||
pub tls: Option<TlsConfig>,
|
||||
/// The secret key for signing and encrypting. **(default: `0`)**
|
||||
///
|
||||
/// **Note:** This field _always_ serializes as a 256-bit array of `0`s to
|
||||
/// aid in preventing leakage of the secret key.
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
#[serde(serialize_with = "SecretKey::serialize_zero")]
|
||||
pub secret_key: SecretKey,
|
||||
/// The directory to store temporary files in. **(default:
|
||||
/// [`std::env::temp_dir`]).
|
||||
|
@ -83,8 +88,8 @@ pub struct Config {
|
|||
}
|
||||
|
||||
impl Default for Config {
|
||||
/// Returns the default configuration based on the compilation profile. This
|
||||
/// is [`Config::debug_default()`] in `debug` and
|
||||
/// Returns the default configuration based on the Rust compilation profile.
|
||||
/// This is [`Config::debug_default()`] in `debug` and
|
||||
/// [`Config::release_default()`] in `release`.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -123,10 +128,13 @@ impl Config {
|
|||
("dev", Some("debug")), ("prod", Some("release")),
|
||||
];
|
||||
|
||||
/// Returns the default configuration for the `debug` profile, irrespective
|
||||
/// of the compilation profile. For the default Rocket will use, which is
|
||||
/// chosen based on the configuration profile, call [`Config::default()`].
|
||||
/// See [Defaults](#Defaults) for specifics.
|
||||
/// Returns the default configuration for the `debug` profile, _irrespective
|
||||
/// of the Rust compilation profile_ and `ROCKET_PROFILE`.
|
||||
///
|
||||
/// This may differ from the configuration used by default,
|
||||
/// [`Config::default()`], which is selected based on the Rust compilation
|
||||
/// profile. See [defaults](#defaults) and [provider
|
||||
/// details](#provider-details) for specifics.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -141,20 +149,24 @@ impl Config {
|
|||
port: 8000,
|
||||
workers: num_cpus::get(),
|
||||
keep_alive: 5,
|
||||
limits: Limits::default(),
|
||||
tls: None,
|
||||
#[cfg(feature = "secrets")]
|
||||
secret_key: SecretKey::zero(),
|
||||
temp_dir: std::env::temp_dir(),
|
||||
log_level: LogLevel::Normal,
|
||||
cli_colors: true,
|
||||
secret_key: SecretKey::zero(),
|
||||
tls: None,
|
||||
limits: Limits::default(),
|
||||
temp_dir: std::env::temp_dir(),
|
||||
ctrlc: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default configuration for the `release` profile,
|
||||
/// irrespective of the compilation profile. For the default Rocket will
|
||||
/// use, which is chosen based on the configuration profile, call
|
||||
/// [`Config::default()`]. See [Defaults](#Defaults) for specifics.
|
||||
/// _irrespective of the Rust compilation profile_ and `ROCKET_PROFILE`.
|
||||
///
|
||||
/// This may differ from the configuration used by default,
|
||||
/// [`Config::default()`], which is selected based on the Rust compilation
|
||||
/// profile. See [defaults](#defaults) and [provider
|
||||
/// details](#provider-details) for specifics.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -200,6 +212,7 @@ impl Config {
|
|||
/// ```
|
||||
pub fn figment() -> Figment {
|
||||
Figment::from(Config::default())
|
||||
.select(Profile::from_env_or("ROCKET_PROFILE", Self::DEFAULT_PROFILE))
|
||||
.merge(Toml::file(Env::var_or("ROCKET_CONFIG", "Rocket.toml")).nested())
|
||||
.merge(Env::prefixed("ROCKET_").ignore(&["PROFILE"]).global())
|
||||
}
|
||||
|
@ -224,6 +237,7 @@ impl Config {
|
|||
///
|
||||
/// let config = rocket::Config::from(figment);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn from<T: Provider>(provider: T) -> Self {
|
||||
let figment = Figment::from(&provider);
|
||||
|
||||
|
@ -235,15 +249,17 @@ impl Config {
|
|||
|
||||
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
|
||||
if !config.secret_key.is_provided() {
|
||||
if figment.profile() != Self::DEBUG_PROFILE {
|
||||
if figment.profile() == Self::DEBUG_PROFILE {
|
||||
// in debug, try to generate a key for a bit more security
|
||||
let key = SecretKey::generate().unwrap_or(SecretKey::zero());
|
||||
config.secret_key = key;
|
||||
} else {
|
||||
crate::logger::try_init(LogLevel::Debug, true, false);
|
||||
error!("secrets enabled in non-`debug` without `secret_key`");
|
||||
error!("secrets enabled in non-debug without `secret_key`");
|
||||
info_!("selected profile: {}", Paint::white(figment.profile()));
|
||||
info_!("disable `secrets` feature or configure a `secret_key`");
|
||||
panic!("aborting due to configuration error(s)")
|
||||
}
|
||||
|
||||
// in debug, generate a key for a bit more security
|
||||
config.secret_key = SecretKey::generate().unwrap_or(SecretKey::zero());
|
||||
}
|
||||
|
||||
config
|
||||
|
@ -290,13 +306,16 @@ impl Config {
|
|||
false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
|
||||
}
|
||||
|
||||
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
|
||||
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] {
|
||||
launch_info_!("secret key: {:?}",
|
||||
Paint::default(&self.secret_key).bold());
|
||||
|
||||
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
|
||||
if !self.secret_key.is_provided() {
|
||||
warn!("secrets enabled without a configured `secret_key`");
|
||||
info_!("disable `secrets` feature or configure a `secret_key`");
|
||||
info_!("this becomes a {} in non-debug profiles", Paint::red("hard error").bold());
|
||||
if !self.secret_key.is_provided() {
|
||||
warn!("secrets enabled without a configured `secret_key`");
|
||||
info_!("disable `secrets` feature or configure a `secret_key`");
|
||||
info_!("this becomes a {} in non-debug profiles",
|
||||
Paint::red("hard error").bold());
|
||||
}
|
||||
}
|
||||
|
||||
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold());
|
||||
|
@ -339,20 +358,20 @@ impl Provider for Config {
|
|||
|
||||
#[track_caller]
|
||||
fn data(&self) -> Result<Map<Profile, Dict>> {
|
||||
#[allow(unused_mut)]
|
||||
let mut map: Map<Profile, Dict> = Serialized::defaults(self).data()?;
|
||||
|
||||
// We need to special-case `secret_key` since its serializer zeroes.
|
||||
#[cfg(feature = "secrets")]
|
||||
if !self.secret_key.is_zero() {
|
||||
if let Some(map) = map.get_mut(&Profile::Default) {
|
||||
map.insert("secret_key".into(), self.secret_key.master().into());
|
||||
map.insert("secret_key".into(), self.secret_key.key.master().into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
fn profile(&self) -> Option<Profile> {
|
||||
Some(Profile::from_env_or("ROCKET_PROFILE", Self::DEFAULT_PROFILE))
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::async_trait]
|
||||
|
@ -370,6 +389,7 @@ pub fn pretty_print_error(error: figment::Error) {
|
|||
|
||||
crate::logger::try_init(LogLevel::Debug, true, false);
|
||||
|
||||
error!("Rocket configuration extraction from provider failed.");
|
||||
for e in error {
|
||||
fn w<T: std::fmt::Display>(v: T) -> Paint<T> { Paint::white(v) }
|
||||
|
||||
|
|
|
@ -70,13 +70,14 @@
|
|||
//! An application that wants to use Rocket's defaults for [`Config`], but not
|
||||
//! its configuration sources, while allowing the application to be configured
|
||||
//! via an `App.toml` file that uses top-level keys as profiles (`.nested()`)
|
||||
//! and `APP_` environment variables as global overrides (`.global()`), can be
|
||||
//! structured as follows:
|
||||
//! and `APP_` environment variables as global overrides (`.global()`), and
|
||||
//! `APP_PROFILE` to configure the selected profile, can be structured as
|
||||
//! follows:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[macro_use] extern crate rocket;
|
||||
//! use serde::{Serialize, Deserialize};
|
||||
//! use figment::{Figment, providers::{Format, Toml, Serialized, Env}};
|
||||
//! use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
|
||||
//! use rocket::fairing::AdHoc;
|
||||
//!
|
||||
//! #[derive(Debug, Deserialize, Serialize)]
|
||||
|
@ -96,7 +97,8 @@
|
|||
//! let figment = Figment::from(rocket::Config::default())
|
||||
//! .merge(Serialized::defaults(Config::default()))
|
||||
//! .merge(Toml::file("App.toml").nested())
|
||||
//! .merge(Env::prefixed("APP_").global());
|
||||
//! .merge(Env::prefixed("APP_").global())
|
||||
//! .select(Profile::from_env_or("APP_PROFILE", "default"));
|
||||
//!
|
||||
//! rocket::custom(figment)
|
||||
//! .mount("/", routes![/* .. */])
|
||||
|
@ -109,17 +111,22 @@
|
|||
//! [`Toml`]: figment::providers::Toml
|
||||
//! [`Env`]: figment::providers::Env
|
||||
|
||||
mod secret_key;
|
||||
mod config;
|
||||
mod tls;
|
||||
|
||||
#[cfg(feature = "secrets")]
|
||||
mod secret_key;
|
||||
|
||||
#[doc(hidden)] pub use config::pretty_print_error;
|
||||
|
||||
pub use config::Config;
|
||||
pub use crate::logger::LogLevel;
|
||||
pub use secret_key::SecretKey;
|
||||
pub use tls::TlsConfig;
|
||||
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub use secret_key::SecretKey;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
@ -132,26 +139,21 @@ mod tests {
|
|||
#[test]
|
||||
fn test_default_round_trip() {
|
||||
figment::Jail::expect_with(|_| {
|
||||
let figment = Figment::from(Config::default());
|
||||
let original = Config::figment();
|
||||
let profile = original.profile().clone();
|
||||
let roundtrip = Figment::from(Config::from(&original)).select(profile);
|
||||
for figment in &[original, roundtrip] {
|
||||
assert_eq!(figment.profile(), Config::DEFAULT_PROFILE);
|
||||
|
||||
assert_eq!(figment.profile(), Config::DEFAULT_PROFILE);
|
||||
#[cfg(debug_assertions)] assert_eq!(figment.profile(), Config::DEBUG_PROFILE);
|
||||
#[cfg(not(debug_assertions))] assert_eq!(figment.profile(), Config::RELEASE_PROFILE);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(figment.profile(), Config::DEBUG_PROFILE);
|
||||
let config: Config = figment.extract().unwrap();
|
||||
assert_eq!(config, Config::default());
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert_eq!(figment.profile(), Config::RELEASE_PROFILE);
|
||||
|
||||
let config: Config = figment.extract().unwrap();
|
||||
assert_eq!(config, Config::default());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(config, Config::debug_default());
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert_eq!(config, Config::release_default());
|
||||
|
||||
assert_eq!(Config::from(Config::default()), Config::default());
|
||||
#[cfg(debug_assertions)] assert_eq!(config, Config::debug_default());
|
||||
#[cfg(not(debug_assertions))] assert_eq!(config, Config::release_default());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
@ -161,15 +163,15 @@ mod tests {
|
|||
fn test_profile_env() {
|
||||
figment::Jail::expect_with(|jail| {
|
||||
jail.set_env("ROCKET_PROFILE", "debug");
|
||||
let figment = Figment::from(Config::default());
|
||||
let figment = Config::figment();
|
||||
assert_eq!(figment.profile(), "debug");
|
||||
|
||||
jail.set_env("ROCKET_PROFILE", "release");
|
||||
let figment = Figment::from(Config::default());
|
||||
let figment = Config::figment();
|
||||
assert_eq!(figment.profile(), "release");
|
||||
|
||||
jail.set_env("ROCKET_PROFILE", "random");
|
||||
let figment = Figment::from(Config::default());
|
||||
let figment = Config::figment();
|
||||
assert_eq!(figment.profile(), "random");
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
|
||||
use serde::{de, ser, Deserialize, Serialize};
|
||||
|
||||
|
@ -18,36 +17,54 @@ enum Kind {
|
|||
/// A `SecretKey` is primarily used by [private cookies]. See the [configuration
|
||||
/// guide] for further details. It can be configured from 256-bit random
|
||||
/// material or a 512-bit master key, each as either a base64-encoded string or
|
||||
/// raw bytes. When compiled in debug mode with the `secrets` feature enabled, a
|
||||
/// raw bytes.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::config::Config;
|
||||
///
|
||||
/// let figment = Config::figment()
|
||||
/// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
|
||||
///
|
||||
/// let config = Config::from(figment);
|
||||
/// assert!(!config.secret_key.is_zero());
|
||||
/// ```
|
||||
///
|
||||
/// When configured in the debug profile with the `secrets` feature enabled, a
|
||||
/// key set a `0` is automatically regenerated from the OS's random source if
|
||||
/// available.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
|
||||
/// ```rust,ignore
|
||||
/// # // FIXME: Don't special case `SecretKey`.
|
||||
/// use rocket::config::Config;
|
||||
///
|
||||
/// # #[cfg(feature = "secrets")]
|
||||
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
|
||||
/// # #[cfg(not(feature = "secrets"))]
|
||||
/// # assert!(rocket::Config::from(figment).secret_key.is_zero());
|
||||
/// let figment = Config::figment()
|
||||
/// .merge(("secret_key", vec![0u8; 64]))
|
||||
/// .select("debug");
|
||||
///
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(("secret_key", vec![0u8; 64]));
|
||||
/// let config = Config::from(figment);
|
||||
/// assert!(!config.secret_key.is_zero());
|
||||
/// ```
|
||||
///
|
||||
/// # /* as far as I can tell, there's no way to test this properly
|
||||
/// # https://github.com/rust-lang/cargo/issues/6570
|
||||
/// # https://github.com/rust-lang/cargo/issues/4737
|
||||
/// # https://github.com/rust-lang/rust/issues/43031
|
||||
/// assert!(!rocket::Config::from(figment).secret_key.is_zero());
|
||||
/// # */
|
||||
/// When running in any other profile with the `secrets` feature enabled,
|
||||
/// providing a key of `0` or not provided a key at all results in a failure at
|
||||
/// launch-time:
|
||||
///
|
||||
/// ```rust,should_panic,ignore
|
||||
/// # // FIXME: Don't special case `SecretKey` checking on test/unsafe_key.
|
||||
/// use rocket::config::Config;
|
||||
///
|
||||
/// let figment = Config::figment()
|
||||
/// .merge(("secret_key", vec![0u8; 64]))
|
||||
/// .select("staging");
|
||||
///
|
||||
/// let config = Config::from(figment);
|
||||
/// ```
|
||||
///
|
||||
/// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies
|
||||
/// [configuration guide]: https://rocket.rs/master/guide/configuration/#secret-key
|
||||
#[derive(Clone)]
|
||||
pub struct SecretKey {
|
||||
key: Key,
|
||||
pub(crate) key: Key,
|
||||
provided: bool,
|
||||
}
|
||||
|
||||
|
@ -142,14 +159,12 @@ impl SecretKey {
|
|||
pub fn is_provided(&self) -> bool {
|
||||
self.provided && !self.is_zero()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl Deref for SecretKey {
|
||||
type Target = Key;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.key
|
||||
/// Serialize as `zero` to avoid key leakage.
|
||||
pub(crate) fn serialize_zero<S>(&self, ser: S) -> Result<S::Ok, S::Error>
|
||||
where S: ser::Serializer
|
||||
{
|
||||
ser.serialize_bytes(&[0; 32][..])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,13 +184,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a SecretKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for SecretKey {
|
||||
fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
// We encode as "zero" to avoid leaking the key.
|
||||
ser.serialize_bytes(&[0; 32][..])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SecretKey {
|
||||
fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
use {binascii::{b64decode, hex2bin}, de::Unexpected::Str};
|
||||
|
|
|
@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize};
|
|||
/// The following example illustrates manual configuration:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// use rocket::Config;
|
||||
///
|
||||
/// let figment = rocket::Config::figment()
|
||||
/// .merge(("tls.certs", "strings/are/paths/certs.pem"))
|
||||
/// .merge(("tls.key", vec![0; 32]));
|
||||
///
|
||||
|
@ -79,8 +80,9 @@ impl TlsConfig {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// use rocket::Config;
|
||||
///
|
||||
/// let figment = Config::figment()
|
||||
/// .merge(("tls.certs", vec![0; 32]))
|
||||
/// .merge(("tls.key", "/etc/ssl/key.pem"));
|
||||
///
|
||||
|
@ -101,9 +103,10 @@ impl TlsConfig {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rocket::figment::Figment;
|
||||
/// # use std::path::Path;
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// use std::path::Path;
|
||||
/// use rocket::Config;
|
||||
///
|
||||
/// let figment = Config::figment()
|
||||
/// .merge(("tls.certs", vec![0; 32]))
|
||||
/// .merge(("tls.key", "/etc/ssl/key.pem"));
|
||||
///
|
||||
|
|
|
@ -2,41 +2,11 @@ use std::fmt;
|
|||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::Header;
|
||||
use crate::Config;
|
||||
use crate::http::private::cookie;
|
||||
|
||||
pub use cookie::{Cookie, SameSite, Iter};
|
||||
#[doc(hidden)] pub use self::key::*;
|
||||
|
||||
/// Types and methods to manage a `Key` when private cookies are enabled.
|
||||
#[cfg(feature = "private-cookies")]
|
||||
mod key {
|
||||
pub use cookie::Key;
|
||||
}
|
||||
|
||||
/// Types and methods to manage a `Key` when private cookies are disabled.
|
||||
#[cfg(not(feature = "private-cookies"))]
|
||||
#[allow(missing_docs)]
|
||||
mod key {
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Key;
|
||||
|
||||
impl Key {
|
||||
pub fn from(_: &[u8]) -> Self { Key }
|
||||
pub fn derive_from(_: &[u8]) -> Self { Key }
|
||||
pub fn generate() -> Self { Key }
|
||||
pub fn try_generate() -> Option<Self> { Some(Key) }
|
||||
pub fn master(&self) -> &[u8] {
|
||||
static ZERO: &'static [u8; 64] = &[0; 64];
|
||||
&ZERO[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Key {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use self::cookie::{Cookie, SameSite, Iter};
|
||||
|
||||
/// Collection of one or more HTTP cookies.
|
||||
///
|
||||
|
@ -133,7 +103,7 @@ mod key {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # #[cfg(feature = "private-cookies")] {
|
||||
/// # #[cfg(feature = "secrets")] {
|
||||
/// use rocket::http::Status;
|
||||
/// use rocket::outcome::IntoOutcome;
|
||||
/// use rocket::request::{self, Request, FromRequest};
|
||||
|
@ -187,16 +157,16 @@ mod key {
|
|||
/// 32`.
|
||||
pub struct CookieJar<'a> {
|
||||
jar: cookie::CookieJar,
|
||||
key: &'a Key,
|
||||
ops: Mutex<Vec<Op>>,
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> Clone for CookieJar<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
CookieJar {
|
||||
jar: self.jar.clone(),
|
||||
key: self.key,
|
||||
ops: Mutex::new(self.ops.lock().clone()),
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,6 +186,15 @@ impl Op {
|
|||
}
|
||||
|
||||
impl<'a> CookieJar<'a> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(config: &'a Config) -> Self {
|
||||
CookieJar::from(cookie::CookieJar::new(), config)
|
||||
}
|
||||
|
||||
pub(crate) fn from(jar: cookie::CookieJar, config: &'a Config) -> Self {
|
||||
CookieJar { jar, config, ops: Mutex::new(Vec::new()) }
|
||||
}
|
||||
|
||||
/// Returns a reference to the _original_ `Cookie` inside this container
|
||||
/// with the name `name`. If no such cookie exists, returns `None`.
|
||||
///
|
||||
|
@ -258,10 +237,10 @@ impl<'a> CookieJar<'a> {
|
|||
/// let cookie = jar.get_private("name");
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
|
||||
self.jar.private(&*self.key).get(name)
|
||||
self.jar.private(&self.config.secret_key.key).get(name)
|
||||
}
|
||||
|
||||
/// Returns a reference to the _original or pending_ `Cookie` inside this
|
||||
|
@ -308,11 +287,11 @@ impl<'a> CookieJar<'a> {
|
|||
/// let pending_cookie = jar.get_private_pending("name");
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub fn get_private_pending(&self, name: &str) -> Option<Cookie<'static>> {
|
||||
let cookie = self.get_pending(name)?;
|
||||
self.jar.private(&*self.key).decrypt(cookie)
|
||||
self.jar.private(&self.config.secret_key.key).decrypt(cookie)
|
||||
}
|
||||
|
||||
/// Adds `cookie` to this collection.
|
||||
|
@ -374,7 +353,7 @@ impl<'a> CookieJar<'a> {
|
|||
/// jar.add_private(Cookie::new("name", "value"));
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub fn add_private(&self, mut cookie: Cookie<'static>) {
|
||||
Self::set_private_defaults(&mut cookie);
|
||||
|
@ -428,7 +407,7 @@ impl<'a> CookieJar<'a> {
|
|||
/// jar.remove_private(Cookie::named("name"));
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub fn remove_private(&self, mut cookie: Cookie<'static>) {
|
||||
if cookie.path().is_none() {
|
||||
|
@ -460,42 +439,26 @@ impl<'a> CookieJar<'a> {
|
|||
pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> {
|
||||
self.jar.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// WARNING: These are unstable! Do not use outside of Rocket!
|
||||
#[doc(hidden)]
|
||||
impl<'a> CookieJar<'a> {
|
||||
#[inline(always)]
|
||||
pub fn new(key: &'a Key) -> Self {
|
||||
CookieJar {
|
||||
jar: cookie::CookieJar::new(),
|
||||
key, ops: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from(jar: cookie::CookieJar, key: &'a Key) -> CookieJar<'a> {
|
||||
CookieJar { jar, key, ops: Mutex::new(Vec::new()) }
|
||||
}
|
||||
|
||||
/// Removes all delta cookies.
|
||||
#[inline(always)]
|
||||
pub fn reset_delta(&self) {
|
||||
pub(crate) fn reset_delta(&self) {
|
||||
self.ops.lock().clear();
|
||||
}
|
||||
|
||||
/// TODO: This could be faster by just returning the cookies directly via
|
||||
/// an ordered hash-set of sorts.
|
||||
#[inline(always)]
|
||||
pub fn take_delta_jar(&self) -> cookie::CookieJar {
|
||||
pub(crate) fn take_delta_jar(&self) -> cookie::CookieJar {
|
||||
let ops = std::mem::replace(&mut *self.ops.lock(), Vec::new());
|
||||
let mut jar = cookie::CookieJar::new();
|
||||
|
||||
for op in ops {
|
||||
match op {
|
||||
Op::Add(c, false) => jar.add(c),
|
||||
#[cfg(feature = "private-cookies")]
|
||||
Op::Add(c, true) => jar.private_mut(self.key).add(c),
|
||||
#[cfg(feature = "secrets")]
|
||||
Op::Add(c, true) => {
|
||||
jar.private_mut(&self.config.secret_key.key).add(c);
|
||||
}
|
||||
Op::Remove(mut c, _) => {
|
||||
if self.jar.get(c.name()).is_some() {
|
||||
c.make_removal();
|
||||
|
@ -514,16 +477,16 @@ impl<'a> CookieJar<'a> {
|
|||
|
||||
/// Adds an original `cookie` to this collection.
|
||||
#[inline(always)]
|
||||
pub fn add_original(&mut self, cookie: Cookie<'static>) {
|
||||
pub(crate) fn add_original(&mut self, cookie: Cookie<'static>) {
|
||||
self.jar.add_original(cookie)
|
||||
}
|
||||
|
||||
/// Adds an original, private `cookie` to the collection.
|
||||
#[inline(always)]
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
pub fn add_original_private(&mut self, cookie: Cookie<'static>) {
|
||||
self.jar.private_mut(&*self.key).add_original(cookie);
|
||||
#[inline(always)]
|
||||
pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) {
|
||||
self.jar.private_mut(&self.config.secret_key.key).add_original(cookie);
|
||||
}
|
||||
|
||||
/// For each property mentioned below, this method checks if there is a
|
||||
|
@ -552,7 +515,7 @@ impl<'a> CookieJar<'a> {
|
|||
/// * `HttpOnly`: `true`
|
||||
/// * `Expires`: 1 week from now
|
||||
///
|
||||
#[cfg(feature = "private-cookies")]
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
fn set_private_defaults(cookie: &mut Cookie<'static>) {
|
||||
if cookie.path().is_none() {
|
||||
|
@ -586,16 +549,5 @@ impl fmt::Debug for CookieJar<'_> {
|
|||
.field("pending", &pending)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cookie<'_>> for Header<'static> {
|
||||
fn from(cookie: Cookie<'_>) -> Header<'static> {
|
||||
Header::new("Set-Cookie", cookie.encoded().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cookie<'_>> for Header<'static> {
|
||||
fn from(cookie: &Cookie<'_>) -> Header<'static> {
|
||||
Header::new("Set-Cookie", cookie.encoded().to_string())
|
||||
}
|
||||
}
|
|
@ -133,6 +133,9 @@ pub mod http {
|
|||
|
||||
#[doc(inline)]
|
||||
pub use rocket_http::*;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::cookies::*;
|
||||
}
|
||||
|
||||
mod shutdown;
|
||||
|
@ -142,6 +145,7 @@ mod server;
|
|||
mod codegen;
|
||||
mod ext;
|
||||
mod state;
|
||||
mod cookies;
|
||||
|
||||
#[doc(hidden)] pub use log::{info, warn, error, debug};
|
||||
#[doc(inline)] pub use crate::response::Response;
|
||||
|
|
|
@ -89,7 +89,7 @@ impl<'c> LocalResponse<'c> {
|
|||
|
||||
async move {
|
||||
let response: Response<'c> = f(request).await;
|
||||
let mut cookies = CookieJar::new(&request.state.config.secret_key);
|
||||
let mut cookies = CookieJar::new(&request.state.config);
|
||||
for cookie in response.cookies() {
|
||||
cookies.add_original(cookie.into_owned());
|
||||
}
|
||||
|
|
|
@ -149,9 +149,9 @@ macro_rules! pub_client_impl {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
|
||||
let key = &self.rocket().config.secret_key;
|
||||
let config = &self.rocket().config;
|
||||
let jar = self._with_raw_cookies(|jar| jar.clone());
|
||||
crate::http::CookieJar::from(jar, key)
|
||||
crate::http::CookieJar::from(jar, config)
|
||||
}
|
||||
|
||||
req_method!($import, "GET", get, Method::Get);
|
||||
|
|
|
@ -89,7 +89,7 @@ impl<'r> Request<'r> {
|
|||
managed: &rocket.managed_state,
|
||||
shutdown: &rocket.shutdown_handle,
|
||||
route: Atomic::new(None),
|
||||
cookies: CookieJar::new(&rocket.config.secret_key),
|
||||
cookies: CookieJar::new(&rocket.config),
|
||||
accept: Storage::new(),
|
||||
content_type: Storage::new(),
|
||||
cache: Arc::new(<Container![Send + Sync]>::new()),
|
||||
|
|
|
@ -52,6 +52,7 @@ impl Rocket {
|
|||
/// rocket::ignite()
|
||||
/// # };
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn ignite() -> Rocket {
|
||||
Rocket::custom(Config::figment())
|
||||
}
|
||||
|
@ -67,19 +68,20 @@ impl Rocket {
|
|||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust
|
||||
/// use figment::{Figment, providers::{Toml, Env, Format}};
|
||||
///
|
||||
/// #[rocket::launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// let figment = Figment::from(rocket::Config::default())
|
||||
/// .merge(Toml::file("MyApp.toml").nested())
|
||||
/// .merge(Env::prefixed("MY_APP_"));
|
||||
/// .merge(Env::prefixed("MY_APP_").global());
|
||||
///
|
||||
/// rocket::custom(figment)
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
|
||||
let (config, figment) = (Config::from(&provider), Figment::from(provider));
|
||||
logger::try_init(config.log_level, config.cli_colors, false);
|
||||
|
@ -529,7 +531,7 @@ impl Rocket {
|
|||
|
||||
#[cfg(feature = "tls")]
|
||||
let server = {
|
||||
use crate::http::tls::bind_tls;
|
||||
use crate::http::private::tls::bind_tls;
|
||||
|
||||
if let Some(tls_config) = &self.config.tls {
|
||||
let (certs, key) = tls_config.to_readers().map_err(ErrorKind::Io)?;
|
||||
|
|
|
@ -18,8 +18,7 @@ mod test_absolute_uris_okay {
|
|||
|
||||
#[test]
|
||||
fn redirect_works() {
|
||||
let rocket = rocket::ignite().mount("/", routes![google, redirect]);
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
let client = Client::debug("/", routes![google, redirect]).unwrap();
|
||||
|
||||
let response = client.get("/google").dispatch();
|
||||
let location = response.headers().get_one("Location");
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg(feature = "secrets")]
|
||||
|
||||
use rocket::figment::Figment;
|
||||
use rocket::config::{Config, SecretKey};
|
||||
|
||||
|
|
|
@ -14,8 +14,11 @@ mod limits_tests {
|
|||
use rocket::data::Limits;
|
||||
|
||||
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
|
||||
let limits = Limits::default().limit("form", limit.into());
|
||||
let config = rocket::Config::figment().merge(("limits", limits));
|
||||
let config = rocket::Config {
|
||||
limits: Limits::default().limit("form", limit.into()),
|
||||
..rocket::Config::debug_default()
|
||||
};
|
||||
|
||||
rocket::custom(config).mount("/", routes![super::index])
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +1,41 @@
|
|||
#[cfg(feature = "secrets")]
|
||||
mod private_cookies {
|
||||
use rocket::http::CookieJar;
|
||||
#![cfg(feature = "secrets")]
|
||||
|
||||
#[rocket::get("/")]
|
||||
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
|
||||
match cookies.get_private("cookie_name") {
|
||||
Some(cookie) => Some(cookie.value().into()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
use rocket::http::CookieJar;
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rocket::routes;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::http::{Cookie, Status};
|
||||
|
||||
#[test]
|
||||
fn private_cookie_is_returned() {
|
||||
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
|
||||
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
|
||||
let response = req.dispatch();
|
||||
|
||||
assert_eq!(response.headers().get_one("Set-Cookie"), None);
|
||||
assert_eq!(response.into_string(), Some("cookie_value".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_cookie_is_not_returned() {
|
||||
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
|
||||
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
|
||||
let response = req.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
#[rocket::get("/")]
|
||||
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
|
||||
match cookies.get_private("cookie_name") {
|
||||
Some(cookie) => Some(cookie.value().into()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rocket::routes;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::http::{Cookie, Status};
|
||||
|
||||
#[test]
|
||||
fn private_cookie_is_returned() {
|
||||
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
|
||||
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
|
||||
let response = req.dispatch();
|
||||
|
||||
assert_eq!(response.headers().get_one("Set-Cookie"), None);
|
||||
assert_eq!(response.into_string(), Some("cookie_value".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regular_cookie_is_not_returned() {
|
||||
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
|
||||
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
|
||||
let response = req.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
#[cfg(feature = "secrets")]
|
||||
mod with_secrets {
|
||||
use rocket::http::{CookieJar, Cookie};
|
||||
#![cfg(feature = "secrets")]
|
||||
|
||||
#[rocket::get("/")]
|
||||
fn index(jar: &CookieJar<'_>) {
|
||||
let session_cookie = Cookie::build("key", "value").expires(None);
|
||||
jar.add_private(session_cookie.finish());
|
||||
}
|
||||
use rocket::http::{CookieJar, Cookie};
|
||||
|
||||
mod test_session_cookies {
|
||||
use super::*;
|
||||
use rocket::local::blocking::Client;
|
||||
#[rocket::get("/")]
|
||||
fn index(jar: &CookieJar<'_>) {
|
||||
let session_cookie = Cookie::build("key", "value").expires(None);
|
||||
jar.add_private(session_cookie.finish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_cookie_is_session() {
|
||||
let rocket = rocket::ignite().mount("/", rocket::routes![index]);
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
mod test_session_cookies {
|
||||
use super::*;
|
||||
use rocket::local::blocking::Client;
|
||||
|
||||
let response = client.get("/").dispatch();
|
||||
let cookie = response.cookies().get_private("key").unwrap();
|
||||
assert_eq!(cookie.expires_datetime(), None);
|
||||
}
|
||||
#[test]
|
||||
fn session_cookie_is_session() {
|
||||
let rocket = rocket::ignite().mount("/", rocket::routes![index]);
|
||||
let client = Client::tracked(rocket).unwrap();
|
||||
|
||||
let response = client.get("/").dispatch();
|
||||
let cookie = response.cookies().get_private("key").unwrap();
|
||||
assert_eq!(cookie.expires_datetime(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,14 +314,14 @@ fn rocket() -> _ {
|
|||
More involved, consider an application that wants to use Rocket's defaults for
|
||||
[`Config`], but not its configuration sources, while allowing the application to
|
||||
be configured via an `App.toml` file that uses top-level keys as profiles
|
||||
(`.nested()`) and `APP_` environment variables as global overrides
|
||||
(`.global()`):
|
||||
(`.nested()`), `APP_` environment variables as global overrides (`.global()`),
|
||||
and `APP_PROFILE` to configure the selected profile:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use figment::{Figment, providers::{Format, Toml, Serialized, Env}};
|
||||
use figment::{Figment, Profile, providers::{Format, Toml, Serialized, Env}};
|
||||
use rocket::fairing::AdHoc;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
@ -341,7 +341,8 @@ fn rocket() -> _ {
|
|||
let figment = Figment::from(rocket::Config::default())
|
||||
.merge(Serialized::defaults(Config::default()))
|
||||
.merge(Toml::file("App.toml").nested())
|
||||
.merge(Env::prefixed("APP_").global());
|
||||
.merge(Env::prefixed("APP_").global())
|
||||
.select(Profile::from_env_or("APP_PROFILE", "default"));
|
||||
|
||||
rocket::custom(figment)
|
||||
.mount("/", routes![/* .. */])
|
||||
|
|
Loading…
Reference in New Issue