Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ The following configuration options are available.
| <span id="SYNC_TOKENSERVER__STATSD_LABEL"></span>SYNC_TOKENSERVER__STATSD_LABEL | syncstorage.tokenserver | StatsD metrics label prefix |
| <span id="SYNC_TOKENSERVER__TOKEN_DURATION"></span>SYNC_TOKENSERVER__TOKEN_DURATION | 3600 | Token TTL (1 hour) |
| <span id="SYNC_TOKENSERVER__FXA_WEBHOOK_ENABLED"></span>SYNC_TOKENSERVER__FXA_WEBHOOK_ENABLED | false | Enable the FxA webhook endpoint. When disabled, the route is not registered. |
| <span id="SYNC_TOKENSERVER__ALLOW_NEW_USERS"></span>SYNC_TOKENSERVER__ALLOW_NEW_USERS | true | Whether new users may be created. When disabled, only previously registered users can use the tokenserver service. |
| <span id="SYNC_TOKENSERVER__FXA_WEBHOOK_METRICS_ONLY"></span>SYNC_TOKENSERVER__FXA_WEBHOOK_METRICS_ONLY | false | Run the FxA webhook handler in metrics-only mode. Received events are counted but not processed. Only used if `FXA_WEBHOOK_ENABLED` is true. |
| <span id="SYNC_TOKENSERVER__FXA_WEBHOOK_SET_CLIENT_ID"></span>SYNC_TOKENSERVER__FXA_WEBHOOK_SET_CLIENT_ID | None | Expected `aud` of FxA Security Event Tokens. Required for account event webhooks. |
| <span id="SYNC_TOKENSERVER__FXA_WEBHOOK_SET_ISSUER"></span>SYNC_TOKENSERVER__FXA_WEBHOOK_SET_ISSUER | None | Expected `iss` of FxA Security Event Tokens. Required for account event webhooks. |
Expand Down
3 changes: 3 additions & 0 deletions syncserver/src/tokenserver/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ impl FromRequest for TokenserverRequest {
client_state: auth_data.client_state.clone(),
keys_changed_at: auth_data.keys_changed_at,
capacity_release_rate: state.node_capacity_release_rate,
allow_new_users: state.allow_new_users,
})
.await?;
log_items_mutator.insert("first_seen_at".to_owned(), user.first_seen_at.to_string());
Expand Down Expand Up @@ -1345,6 +1346,7 @@ mod tests {
token_duration: TOKEN_DURATION,
set_verifiers: Vec::new(),
fxa_webhook_enabled: false,
allow_new_users: true,
fxa_webhook_metrics_only: false,
}
}
Expand All @@ -1368,6 +1370,7 @@ mod tests {
token_duration: TOKEN_DURATION,
set_verifiers,
fxa_webhook_enabled: true,
allow_new_users: true,
fxa_webhook_metrics_only: false,
}
}
Expand Down
1 change: 1 addition & 0 deletions syncserver/src/tokenserver/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ mod tests {
token_duration: 3600,
set_verifiers,
fxa_webhook_enabled: true,
allow_new_users: true,
fxa_webhook_metrics_only: false,
}
}
Expand Down
2 changes: 2 additions & 0 deletions syncserver/src/tokenserver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct ServerState {
pub token_duration: u64,
pub set_verifiers: Vec<SETVerifierImpl>,
pub fxa_webhook_enabled: bool,
pub allow_new_users: bool,
pub fxa_webhook_metrics_only: bool,
}

Expand Down Expand Up @@ -111,6 +112,7 @@ impl ServerState {
token_duration: settings.token_duration,
set_verifiers,
fxa_webhook_enabled: settings.fxa_webhook_enabled,
allow_new_users: settings.allow_new_users,
fxa_webhook_metrics_only: settings.fxa_webhook_metrics_only,
})
}
Expand Down
17 changes: 17 additions & 0 deletions tokenserver-db-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub struct DbError {
}

impl DbError {
pub fn is_user_not_created(user: String) -> Self {
DbErrorKind::UserNotCreated(user).into()
}
Comment thread
HorlogeSkynet marked this conversation as resolved.

pub fn internal(msg: String) -> Self {
DbErrorKind::Internal(msg).into()
}
Expand All @@ -26,6 +30,11 @@ impl DbError {
DbErrorKind::PoolTimeout(timeout_type).into()
}

#[cfg(debug_assertions)]
pub fn is_user_not_created(&self) -> bool {
matches!(&self.kind, DbErrorKind::UserNotCreated(_))
}
Comment thread
HorlogeSkynet marked this conversation as resolved.

#[cfg(debug_assertions)]
pub fn is_diesel_not_found(&self) -> bool {
matches!(&self.kind, DbErrorKind::Sql(e) if e.is_diesel_not_found())
Expand Down Expand Up @@ -59,6 +68,9 @@ impl ReportableError for DbError {

#[derive(Debug, Error)]
enum DbErrorKind {
#[error("Specified user does not exist (new users are disallowed): {}", _0)]
UserNotCreated(String),

#[error("{}", _0)]
Sql(SqlError),

Expand All @@ -72,6 +84,11 @@ enum DbErrorKind {
impl From<DbErrorKind> for DbError {
fn from(kind: DbErrorKind) -> Self {
match kind {
DbErrorKind::UserNotCreated(_) => Self {
kind,
status: StatusCode::FORBIDDEN,
backtrace: Box::new(Backtrace::new_unresolved()),
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When impl From<DbError> for TokenserverError converts an error of this type we'd end up with a HTTP 503. That's misleading for the clients. (We don't want them to retry at the very least.) Whatever error we add here for the feature should become a HTTP 403.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By explicitly matching DbErrorKind::UserNotCreated kind in this context, I guess we prevent _ from converting it to a HTTP 503 by default ?
I've updated the pattern matching to throw a 403 FORBIDDEN as you proposed. Tell me whether it suits what you had in mind.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @chenba, this is a little up 😇

DbErrorKind::Sql(ref sqle) => Self {
status: sqle.status,
backtrace: Box::new(sqle.backtrace.clone()),
Expand Down
9 changes: 7 additions & 2 deletions tokenserver-db-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ pub trait Db {
fn metrics(&self) -> &Metrics;

/// Gets the user with the given email and service ID.
/// If one doesn't exist, allocates a new user.
/// If one doesn't exist, and when configuration allows it, allocates a new user.
async fn get_or_create_user(
&mut self,
params: params::GetOrCreateUser,
Expand All @@ -127,7 +127,11 @@ pub trait Db {

if raw_users.is_empty() {
// There are no users in the database with the given email and service ID, so
// allocate a new one.
// allocate a new one if allowed by configuration.
if !params.allow_new_users {
return Err(DbError::user_not_created(params.email.clone()));
}
Comment thread
HorlogeSkynet marked this conversation as resolved.

let allocate_user_result = self
.allocate_user(params.clone() as params::AllocateUser)
.await?;
Expand Down Expand Up @@ -193,6 +197,7 @@ pub trait Db {
client_state: raw_user.client_state.clone(),
keys_changed_at: raw_user.keys_changed_at,
capacity_release_rate: params.capacity_release_rate,
allow_new_users: params.allow_new_users,
})
.await?
};
Expand Down
1 change: 1 addition & 0 deletions tokenserver-db-common/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct GetOrCreateUser {
pub client_state: String,
pub keys_changed_at: Option<i64>,
pub capacity_release_rate: Option<f32>,
pub allow_new_users: bool,
}

pub type AllocateUser = GetOrCreateUser;
Expand Down
66 changes: 66 additions & 0 deletions tokenserver-db/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ async fn test_node_allocation() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;
assert_eq!(user.node, "https://node1");
Expand Down Expand Up @@ -609,6 +610,7 @@ async fn test_allocation_to_least_loaded_node() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -620,6 +622,7 @@ async fn test_allocation_to_least_loaded_node() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -663,6 +666,7 @@ async fn test_allocation_is_not_allowed_to_downed_nodes() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await;
let error = result.unwrap_err();
Expand Down Expand Up @@ -704,6 +708,7 @@ async fn test_allocation_is_not_allowed_to_backoff_nodes() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await;
let error = result.unwrap_err();
Expand Down Expand Up @@ -744,6 +749,7 @@ async fn test_node_reassignment_when_records_are_replaced() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;
let user1 = db
Expand All @@ -768,6 +774,7 @@ async fn test_node_reassignment_when_records_are_replaced() -> DbResult<()> {
client_state: "bbbb".to_owned(),
keys_changed_at: Some(1235),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -816,6 +823,7 @@ async fn test_node_reassignment_not_done_for_retired_users() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -827,6 +835,7 @@ async fn test_node_reassignment_not_done_for_retired_users() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -884,6 +893,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -895,6 +905,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -906,6 +917,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -917,6 +929,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -949,6 +962,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: user.client_state.clone(),
keys_changed_at: user.keys_changed_at,
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -978,6 +992,7 @@ async fn test_node_reassignment_and_removal() -> DbResult<()> {
client_state: user.client_state.clone(),
keys_changed_at: user.keys_changed_at,
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -1034,6 +1049,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1051,6 +1067,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1070,6 +1087,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1087,6 +1105,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1105,6 +1124,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1122,6 +1142,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1140,6 +1161,7 @@ async fn test_gradual_release_of_node_capacity() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await;

Expand Down Expand Up @@ -1185,6 +1207,7 @@ async fn test_correct_created_at_used_during_node_reassignment() -> DbResult<()>
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1204,6 +1227,7 @@ async fn test_correct_created_at_used_during_node_reassignment() -> DbResult<()>
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand Down Expand Up @@ -1245,6 +1269,7 @@ async fn test_correct_created_at_used_during_user_retrieval() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1261,6 +1286,7 @@ async fn test_correct_created_at_used_during_user_retrieval() -> DbResult<()> {
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: true,
})
.await?;

Expand All @@ -1270,6 +1296,46 @@ async fn test_correct_created_at_used_during_user_retrieval() -> DbResult<()> {
Ok(())
}

#[tokio::test]
async fn test_no_new_user_allocation() -> DbResult<()> {
let pool = db_pool().await?;
let mut db = pool.get().await?;

let service_id = db
.get_service_id(params::GetServiceId {
service: "sync-1.5".to_owned(),
})
.await?
.id;

// Add a node
db.post_node(params::PostNode {
service_id,
node: "https://node1".to_owned(),
current_load: 4,
capacity: 8,
available: 1,
..Default::default()
})
.await?;

// Try to create a user
let result = db
.get_or_create_user(params::GetOrCreateUser {
service_id,
generation: 1234,
email: "test4@test.com".to_owned(),
client_state: "aaaa".to_owned(),
keys_changed_at: Some(1234),
capacity_release_rate: None,
allow_new_users: false,
})
.await;
assert!(result.unwrap_err().is_user_not_created());

Ok(())
}

#[tokio::test]
async fn test_latest_created_at() -> DbResult<()> {
let pool = db_pool().await?;
Expand Down
Loading