diff --git a/graphql/schemas/operations.graphql b/graphql/schemas/operations.graphql index b1d754e..3e3c150 100644 --- a/graphql/schemas/operations.graphql +++ b/graphql/schemas/operations.graphql @@ -46,6 +46,14 @@ mutation AcceptTermsAndConditions($serviceProvider: String!) { } } +query GetTermsAndConditionsStatus($serviceProvider: ServiceProviderEnum!) { + get_terms_conditions_status(args: {service_provider: $serviceProvider}) { + serviceProvider + acceptedTerms + acceptDate + } +} + # Employee query GetBusinessOwner($ownerWalletPubKeyId: uuid!) { diff --git a/graphql/schemas/schema_wallet_read.graphql b/graphql/schemas/schema_wallet_read.graphql index ae9dc4c..4554da7 100644 --- a/graphql/schemas/schema_wallet_read.graphql +++ b/graphql/schemas/schema_wallet_read.graphql @@ -13,6 +13,12 @@ directive @cached( refresh: Boolean! = false ) on QUERY +type AcceptTermsResponse { + acceptDate: DateTime + acceptedTerms: Boolean! + serviceProvider: ServiceProviderEnum! +} + input AcceptWalletPkRequestInput { id: String! } @@ -47,6 +53,16 @@ type CreateBackupResponse { scalar DateTime +input GetTermsConditionsStatusInputInput { + service_provider: ServiceProviderEnum +} + +type GetTermsConditionsStatusResponse { + acceptDate: DateTime + acceptedTerms: Boolean! + serviceProvider: ServiceProviderEnum! +} + type MigrationBalanceResponse { balanceAmountSat: BigInteger! } @@ -131,6 +147,15 @@ input RequestSucceededInput { paymentReceivedAt: DateTime! } +enum ServiceProviderEnum { + LIPA_WALLET + POCKET_EXCHANGE +} + +input ServiceProviderInputInput { + service_provider: String +} + type SessionPermit { accessToken: String refreshToken: String @@ -228,14 +253,6 @@ type WalletNode { walletPubKeyId: String } -input accept_terms_args { - pubkey_id: uuid -} - -input accept_terms_conditions_args { - service_provider: String -} - """ columns and relationships of "accepted_terms_conditions" """ @@ -815,55 +832,7 @@ enum cursor_ordering { """mutation root""" type mutation_root { - """ - execute VOLATILE function "accept_terms" which returns "accepted_terms_conditions" - """ - accept_terms( - """ - input parameters for function "accept_terms" - """ - args: accept_terms_args! - - """distinct select on columns""" - distinct_on: [accepted_terms_conditions_select_column!] - - """limit the number of rows returned""" - limit: Int - - """skip the first n rows. Use only with order_by""" - offset: Int - - """sort the rows by one or more columns""" - order_by: [accepted_terms_conditions_order_by!] - - """filter the rows returned""" - where: accepted_terms_conditions_bool_exp - ): accepted_terms_conditions - - """ - execute VOLATILE function "accept_terms_conditions" which returns "accepted_terms_conditions" - """ - accept_terms_conditions( - """ - input parameters for function "accept_terms_conditions" - """ - args: accept_terms_conditions_args! - - """distinct select on columns""" - distinct_on: [accepted_terms_conditions_select_column!] - - """limit the number of rows returned""" - limit: Int - - """skip the first n rows. Use only with order_by""" - offset: Int - - """sort the rows by one or more columns""" - order_by: [accepted_terms_conditions_order_by!] - - """filter the rows returned""" - where: accepted_terms_conditions_bool_exp - ): accepted_terms_conditions + accept_terms_conditions(args: ServiceProviderInputInput): AcceptTermsResponse accept_wallet_acl_by_pk(pk_columns: AcceptWalletPkRequestInput!): WalletAcl create_backup(encryptedBackup: String!, schemaName: String!, schemaVersion: String!): CreateBackupResponse @@ -1080,6 +1049,7 @@ type query_root { """fetch data from the table: "channel_monitor" using primary key columns""" channel_monitor_by_pk(channelId: bytea!, timestamp: timestamptz!, walletId: uuid!): channel_monitor + consumer_service_version: String """ fetch data from the table: "country" @@ -1129,6 +1099,7 @@ type query_root { """ISO 4217""" currencyCode: String! ): currency + get_terms_conditions_status(args: GetTermsConditionsStatusInputInput): GetTermsConditionsStatusResponse migration_balance(nodePubKey: String): MigrationBalanceResponse notification_service_version: String payment_service_version: String @@ -1856,3 +1827,4 @@ input wallet_acl_stream_cursor_value_input { ownerWalletPubKeyId: uuid role: String } + diff --git a/graphql/src/schema.rs b/graphql/src/schema.rs index 057e370..1d11d9d 100644 --- a/graphql/src/schema.rs +++ b/graphql/src/schema.rs @@ -58,6 +58,14 @@ pub struct RefreshSession; )] pub struct AcceptTermsAndConditions; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "schemas/schema_wallet_read.graphql", + query_path = "schemas/operations.graphql", + response_derives = "Debug" +)] +pub struct GetTermsAndConditionsStatus; + #[derive(GraphQLQuery)] #[graphql( schema_path = "schemas/schema_wallet_read.graphql", diff --git a/honey-badger/src/lib.rs b/honey-badger/src/lib.rs index 79b81a8..e6e37c8 100644 --- a/honey-badger/src/lib.rs +++ b/honey-badger/src/lib.rs @@ -29,6 +29,13 @@ pub struct Auth { token: Mutex, } +#[derive(Debug, PartialEq)] +pub struct TermsAndConditionsStatus { + pub accept_date: Option, + pub accepted_terms: bool, + pub service_provider: TermsAndConditions, +} + impl Auth { pub fn new( backend_url: String, @@ -83,6 +90,15 @@ impl Auth { provider.accept_terms_and_conditions(token, terms) } + pub fn get_terms_and_conditions_status( + &self, + terms: TermsAndConditions, + ) -> Result { + let token = self.query_token()?; + let provider = self.provider.lock().unwrap(); + provider.get_terms_and_conditions_status(token, terms) + } + fn get_token_if_valid(&self) -> Option { let now = SystemTime::now(); let token = self.token.lock().unwrap(); diff --git a/honey-badger/src/provider.rs b/honey-badger/src/provider.rs index 656a446..28e2374 100644 --- a/honey-badger/src/provider.rs +++ b/honey-badger/src/provider.rs @@ -1,10 +1,12 @@ use crate::secrets::KeyPair; use crate::signing::sign; +use crate::TermsAndConditionsStatus; use graphql::perro::{invalid_input, permanent_failure, runtime_error, OptionToError}; use graphql::reqwest::blocking::Client; +use graphql::schema::get_terms_and_conditions_status::ServiceProviderEnum; use graphql::schema::*; -use graphql::{build_client, post_blocking}; +use graphql::{build_client, perro, post_blocking}; use graphql::{errors::*, parse_from_rfc3339}; use log::info; use std::time::SystemTime; @@ -16,7 +18,7 @@ pub enum AuthLevel { Employee, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum TermsAndConditions { Lipa, Pocket, @@ -32,6 +34,30 @@ impl From for String { } } +impl From for ServiceProviderEnum { + fn from(value: TermsAndConditions) -> Self { + match value { + TermsAndConditions::Lipa => ServiceProviderEnum::LIPA_WALLET, + TermsAndConditions::Pocket => ServiceProviderEnum::POCKET_EXCHANGE, + } + } +} + +impl TryInto for ServiceProviderEnum { + type Error = perro::Error; + + fn try_into(self) -> std::result::Result { + match self { + ServiceProviderEnum::LIPA_WALLET => Ok(TermsAndConditions::Lipa), + ServiceProviderEnum::POCKET_EXCHANGE => Ok(TermsAndConditions::Pocket), + ServiceProviderEnum::Other(v) => Err(runtime_error( + GraphQlRuntimeErrorCode::CorruptData, + format!("Unknown service provider: {:?}", v), + )), + } + } +} + pub(crate) struct AuthProvider { backend_url: String, auth_level: AuthLevel, @@ -112,6 +138,44 @@ impl AuthProvider { Ok(()) } + pub fn get_terms_and_conditions_status( + &self, + access_token: String, + terms: TermsAndConditions, + ) -> Result { + info!("Requesting T&C status ({:?})...", terms); + if self.auth_level != AuthLevel::Pseudonymous { + return Err(invalid_input( + "Requesting T&C status not supported for auth levels other than Pseudonymous", + )); + } + + let variables = get_terms_and_conditions_status::Variables { + service_provider: terms.into(), + }; + let client = build_client(Some(&access_token))?; + let data = + post_blocking::(&client, &self.backend_url, variables)?; + match data.get_terms_conditions_status { + None => Err(runtime_error( + GraphQlRuntimeErrorCode::RemoteServiceUnavailable, + "Couldn't fetch T&C status.", + )), + Some(d) => { + let accept_date = match d.accept_date { + None => None, + Some(d) => Some(parse_from_rfc3339(&d)?), + }; + + Ok(TermsAndConditionsStatus { + accept_date, + accepted_terms: d.accepted_terms, + service_provider: d.service_provider.try_into()?, + }) + } + } + } + fn run_auth_flow(&mut self) -> Result<(String, String)> { let (access_token, refresh_token, wallet_pub_key_id) = self.start_basic_session()?;