diff --git a/Cargo.lock b/Cargo.lock index 7479a23..a5c9d3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,7 +1035,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "feedback-fusion" -version = "0.1.4" +version = "0.2.0" dependencies = [ "aliri", "aliri_clock", diff --git a/Cargo.toml b/Cargo.toml index db6ecbf..eb41674 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feedback-fusion" -version = "0.1.4" +version = "0.2.0" edition = "2021" license = "MIT" @@ -50,7 +50,7 @@ tonic-health = "0.11.0" tonic-reflection = "0.11.0" tonic-web = "0.11.0" tokio = { version = "1.37.0", features = ["full"] } -tower = "0.4.13" +tower = { version = "0.4.13", feature = ["limit"] } tokio-retry = "0.3" tower-http = { version = "=0.4.4", features = ["trace", "validate-request"] } tracing = "0.1.39" diff --git a/charts/feedback-fusion/Chart.yaml b/charts/feedback-fusion/Chart.yaml index 18bf7cc..3fd6dc7 100644 --- a/charts/feedback-fusion/Chart.yaml +++ b/charts/feedback-fusion/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.7 +version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.1.4" +appVersion: "0.2.0" diff --git a/charts/feedback-fusion/values.yaml b/charts/feedback-fusion/values.yaml index 021cdec..c953931 100644 --- a/charts/feedback-fusion/values.yaml +++ b/charts/feedback-fusion/values.yaml @@ -120,7 +120,6 @@ feedbackFusion: config: secret: feedback-fusion-config # RUST_LOG: INFO - # GLOBAL_RATE_LIMIT: 10 # OIDC_AUDIENCE: "" # OIDC_PROVIDER: "" # diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 4b9343f..7ea0a09 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -6,7 +6,6 @@ You can set the following environment variables: | Environment Variable | Type | Default Value | Description | |-------------------------|-------------------|----------------------------|-----------------------------------------------------------------------------| -| `GLOBAL_RATE_LIMIT` | `u64` | `10` | The global rate limit for requests. | | `OIDC_PROVIDER` | `String` | N/A | The OIDC provider URL. | | `OIDC_AUDIENCE` | `String` | `"feedback-fusion"` | The audience for the OIDC tokens. | | `OIDC_ISSUER` | `Option` | `None` | The optional issuer URL for the OIDC tokens. | @@ -15,6 +14,21 @@ You can set the following environment variables: | `OTLP_ENDPOINT` | `Option` | `None` | The gRPC OTLP endpoint to send the trace spans to | | `SERVICE_NAME` | `String` | `"feedback-fusion"` | Service name used in tracing context | +## Scope Configuration + +| Environment Variable | Description | +|-----------------------------------|------------------------------------| +| `OIDC_SCOPE_API` | Scope for API access | +| `OIDC_SCOPE_WRITE` | Scope for write access | +| `OIDC_SCOPE_READ` | Scope for read access | +| `OIDC_SCOPE_WRITE_TARGET` | Scope for writing targets | +| `OIDC_SCOPE_READ_TARGET` | Scope for reading targets | +| `OIDC_SCOPE_WRITE_PROMPT` | Scope for writing prompts | +| `OIDC_SCOPE_READ_PROMPT` | Scope for reading prompts | +| `OIDC_SCOPE_WRITE_FIELD` | Scope for writing fields | +| `OIDC_SCOPE_READ_FIELD` | Scope for reading fields | +| `OIDC_SCOPE_READ_RESPONSE` | Scope for reading responses | + ## Database Configuration The Backend supports mutliple database backends. The backend will choose the database based on your provided configuration values. diff --git a/src/config.rs b/src/config.rs index 3cd3196..04834ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,35 +34,61 @@ lazy_static! { DatabaseConfiguration::extract().unwrap(); } -#[derive(Deserialize, Debug, Clone, Getters)] -#[get = "pub"] -pub struct Config { - #[serde(default = "default_global_rate_limit")] - global_rate_limit: u64, - oidc_provider: String, - #[serde(default = "default_oidc_audience")] - oidc_audience: String, - oidc_issuer: Option, - config_path: Option, - otlp_endpoint: Option, - #[serde(default = "default_service_name")] - service_name: String -} +macro_rules! config { + (($($ident:ident: $type:ty $(,)? )*), ($($dident:ident: $dtype:ty = $default:expr $(,)?)*)) => { + paste! { + #[derive(Deserialize, Debug, Clone, Getters)] + #[get = "pub"] + pub struct Config { + $( + $ident: $type, + )* + + $( + #[serde(default = "default_" $dident)] + $dident: $dtype, + )* + } -#[inline] -fn default_global_rate_limit() -> u64 { - 10 -} -#[inline] -fn default_oidc_audience() -> String { - "feedback-fusion".to_owned() + $( + #[inline] + fn []() -> $dtype { + $default.to_owned() + } + )* + } + }; } -#[inline] -fn default_service_name() -> String { - "feedback-fusion".to_owned() -} +config!( + ( + oidc_provider: String, + oidc_issuer: Option, + config_path: Option, + otlp_endpoint: Option, + ), + + ( + service_name: String = "feedback-fusion" + oidc_audience: String = "feedback-fusion", + + oidc_scope_api: String = "api:feedback-fusion", + oidc_scope_write: String = "feedback-fusion:write", + oidc_scope_read: String = "feedback-fusion:read", + + oidc_scope_write_target: String = "feedback-fusion:writeTarget", + oidc_scope_read_target: String = "feedback-fusion:readTarget" + + oidc_scope_write_prompt: String = "feedback-fusion:writePrompt", + oidc_scope_read_prompt: String = "feedback-fusion:readPrompt" + + oidc_scope_write_field: String = "feedback-fusion:writeField", + oidc_scope_read_field: String = "feedback-fusion:readField" + + oidc_scope_read_response: String = "feedback-fusion:readResponse" + ) +); #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct InstanceConfig { diff --git a/src/services/oidc.rs b/src/services/oidc.rs index 17e9fc0..275086d 100644 --- a/src/services/oidc.rs +++ b/src/services/oidc.rs @@ -97,15 +97,16 @@ pub async fn authority() -> Result { #[derive(Debug, Clone, Deserialize)] pub struct OIDCClaims { iss: jwt::Issuer, + iat: UnixTime, aud: jwt::Audiences, - nbf: UnixTime, + nbf: Option, exp: UnixTime, scope: Scope, } impl jwt::CoreClaims for OIDCClaims { fn nbf(&self) -> Option { - Some(self.nbf) + Some(self.nbf.unwrap_or(self.iat)) } fn exp(&self) -> Option { Some(self.exp) diff --git a/src/services/v1/mod.rs b/src/services/v1/mod.rs index 3c68ee4..f91f087 100644 --- a/src/services/v1/mod.rs +++ b/src/services/v1/mod.rs @@ -21,7 +21,7 @@ //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. use crate::{database::schema::feedback::Prompt, prelude::*}; -use aliri_oauth2::{policy, scope, HasScope}; +use aliri_oauth2::{policy, HasScope}; use aliri_traits::Policy; use feedback_fusion_common::proto::{ feedback_fusion_v1_server::FeedbackFusionV1, @@ -53,7 +53,7 @@ pub struct PublicFeedbackFusionV1Context { // https://github.com/neoeinstein/aliri/blob/main/aliri_tower/examples/.tonic.rs#L35 macro_rules! handler { - ($handler:path, $self:ident, $request:ident, $policy:ident, $($scope:literal $(,)?)*) => {{ + ($handler:path, $self:ident, $request:ident, $policy:ident, $($scope:expr $(,)?)*) => {{ $policy .evaluate( $request @@ -76,12 +76,12 @@ macro_rules! handler { handler!($handler, $self, $request) }}; - ($handler:path, $self:ident, $request:ident, $($scope:literal $(,)?)*, $target:block) => {{ + ($handler:path, $self:ident, $request:ident, $($scope:expr $(,)?)* => $target:block) => {{ paste! { let policy = policy![ - scope!["api:feedback-fusion"] + aliri_oauth2::Scope::empty().and(aliri_oauth2::scope::ScopeToken::from_string(CONFIG.oidc_scope_api().clone()).unwrap()) $(, - scope![$scope] + aliri_oauth2::Scope::empty().and(aliri_oauth2::scope::ScopeToken::from_string($scope.to_string()).unwrap()) )* ]; @@ -145,11 +145,11 @@ macro_rules! handler { } } }}; - ($handler:path, $self:ident, $request:ident, $($scope:literal $(,)?)*) => {{ + ($handler:path, $self:ident, $request:ident, $($scope:expr $(,)?)*) => {{ let policy = policy![ - scope!["api:feedback-fusion"] + aliri_oauth2::Scope::empty().and(aliri_oauth2::scope::ScopeToken::from_string(CONFIG.oidc_scope_api().clone()).unwrap()) $(, - scope![$scope] + aliri_oauth2::Scope::empty().and(aliri_oauth2::scope::ScopeToken::from_string($scope.to_string()).unwrap()) )* ]; @@ -196,7 +196,8 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { target::create_target, self, request, - "feedback-fusion:write" + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_target() ) } @@ -209,9 +210,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { target::get_target, self, request, - "feedback-fusion:read", - "feedback-fusion:getTarget", - { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } + CONFIG.oidc_scope_read(), + CONFIG.oidc_scope_read_target() + => { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } ) } @@ -224,8 +225,8 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { target::get_targets, self, request, - "feedback-fusion:read", - "feedback-fusion:listTargets" + CONFIG.oidc_scope_read(), + CONFIG.oidc_scope_read_target() ) } @@ -238,9 +239,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { target::update_target, self, request, - "feedback-fusion:write", - "feedback-fusion:putTarget", - { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_target() + => { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } ) } @@ -253,9 +254,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { target::delete_target, self, request, - "feedback-fusion:write", - "feedback-fusion:deleteTarget", - { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_target() + => { Ok::<_, FeedbackFusionError>(Some(request.get_ref().id.clone())) } ) } @@ -268,9 +269,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { prompt::create_prompt, self, request, - "feedback-fusion:write", - "feedback-fusion:writePrompt", - { Ok::<_, FeedbackFusionError>(Some(request.get_ref().target.clone())) } + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_prompt() + => { Ok::<_, FeedbackFusionError>(Some(request.get_ref().target.clone())) } ) } @@ -283,9 +284,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { prompt::get_prompts, self, request, - "feedback-fusion:read", - "feedback-fusion:listPrompts", - { Ok::<_, FeedbackFusionError>(Some(request.get_ref().target.clone())) } + CONFIG.oidc_scope_read(), + CONFIG.oidc_scope_read_prompt() + => { Ok::<_, FeedbackFusionError>(Some(request.get_ref().target.clone())) } ) } @@ -298,9 +299,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { prompt::update_prompt, self, request, - "feedback-fusion:write", - "feedback-fusion:putPrompt", - { + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_prompt() + => { Ok::<_, FeedbackFusionError>( database_request!( Prompt::select_by_id(self.connection(), request.get_ref().id.as_str()) @@ -322,9 +323,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { prompt::delete_prompt, self, request, - "feedback-fusion:write", - "feedback-fusion:deleteTarget", - { + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_prompt() + => { Ok::<_, FeedbackFusionError>( database_request!( Prompt::select_by_id(self.connection(), request.get_ref().id.as_str()) @@ -346,9 +347,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { field::create_field, self, request, - "feedback-fusion:write", - "feedback-fusion:writeField", - { + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_field() + => { Ok::<_, FeedbackFusionError>( database_request!( Prompt::select_by_id(self.connection(), request.get_ref().prompt.as_str()) @@ -370,9 +371,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { field::get_fields, self, request, - "feedback-fusion:read", - "feedback-fusion:listFields", - { + CONFIG.oidc_scope_read(), + CONFIG.oidc_scope_read_field() + => { Ok::<_, FeedbackFusionError>( database_request!( Prompt::select_by_id(self.connection(), request.get_ref().prompt.as_str()) @@ -394,9 +395,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { field::update_field, self, request, - "feedback-fusion:write", - "feedback-fusion:putField", - { + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_field() + => { let prompt: Option = database_request!( self.connection() .query_decode( @@ -420,9 +421,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { field::delete_field, self, request, - "feedback-fusion:write", - "feedback-fusion:deleteField", - { + CONFIG.oidc_scope_write(), + CONFIG.oidc_scope_write_field() + => { let prompt: Option = database_request!( self.connection() .query_decode( @@ -446,9 +447,9 @@ impl FeedbackFusionV1 for FeedbackFusionV1Context { response::get_responses, self, request, - "feedback-fusion:read", - "feedback-fusion:listResponses", - { + CONFIG.oidc_scope_read(), + CONFIG.oidc_scope_read_response() + => { Ok::<_, FeedbackFusionError>( database_request!( Prompt::select_by_id(self.connection(), request.get_ref().prompt.as_str())