Tauri Redirect URI Schema - rust

I'm building a Tauri app and would like to set up OAuth integration with Google. To do so, I will need a URI for the oauth callback, but Tauri is unclear how to configure the schema possibly using this method or with the WindowUrl?
How can I add a URI to my Tauri app so I could like to it like the following example:
myapp://callback
I think it could look something like the following:
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.register_uri_scheme_protocol("myapp", move |app, request| {
# protocol logic here
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

Tauri currently doesn't directly support deep linking. A good alternative that I found was this rust project. After installation you can do something like the following:
#[tauri::command]
async fn start_oauth_server(window: Window) -> Result<u16, String> {
println!("Starting server");
start(None, move |url| {
// Because of the unprotected localhost port, you must verify the URL here.
// Preferebly send back only the token, or nothing at all if you can handle everything else in Rust.
// convert the string to a url
let url = url::Url::parse(&url).unwrap();
// get the code query parameter
let code = url
.query_pairs()
.find(|(k, _)| k == "code")
.unwrap_or_default()
.1;
// get the state query parameter
let state = url
.query_pairs()
.find(|(k, _)| k == "state")
.unwrap_or_default()
.1;
// create map of query parameters
let mut query_params = HashMap::new();
query_params.insert("code".to_string(), code.to_string());
query_params.insert("state".to_string(), state.to_string());
query_params.insert(String::from("redirect_uri"), url.to_string());
if window.emit("redirect_uri", query_params).is_ok() {
println!("Sent redirect_uri event");
} else {
println!("Failed to send redirect_uri event");
}
})
.map_err(|err| err.to_string())
}

Related

Authenticating to MS Graph without user interaction (Rust)

I have a Java application using the Microsoft Graph SDK to read from Azure AD. I am supposed to translate that application to Rust. In Java, i use code similar to this:
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.identity.TokenCachePersistenceOptions;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;
import java.util.List;
public class GraphSdkClient {
public static void testApplicationClient() {
TokenCachePersistenceOptions options = new TokenCachePersistenceOptions()
.setName("ILM-Demo")
.setUnencryptedStorageAllowed(false);
ClientSecretCredentialBuilder builder = new ClientSecretCredentialBuilder()
.clientId("<MyClientId>")
.tenantId("<MyTenantId>")
.clientSecret("<MyClientSecret>")
.tokenCachePersistenceOptions(options);
TokenCredentialAuthProvider provider = new TokenCredentialAuthProvider(
List.of("Calendars.Read", "Calendars.ReadBasic.All"),
builder.build()
);
GraphServiceClient<?> client = GraphServiceClient
.builder()
.authenticationProvider(provider)
.buildClient();
client.me().calendar().calendarView().buildRequest().get();
}
}
It authenticates as an application, using only the client secret. The permission was given half a year ago and as long as the three values from the ClientSecretCredentialBuilder are correct, it works perfectly fine. Now i tried using a similar conecpt in Rust, taken from the graph-rs-sdk crate:
#[cfg(test)]
mod test {
use graph_rs_sdk::oauth::OAuth;
use warp::Filter;
use crate::{CLIENT_ID, CLIENT_SECRET, TENANT_ID};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct ClientCredentialsResponse {
admin_consent: bool,
tenant: String,
}
#[tokio::test]
async fn ut_client_credentials() {
let query = warp::query::<ClientCredentialsResponse>()
.map(Some)
.or_else(|_| async {
Ok::<(Option<ClientCredentialsResponse>,), std::convert::Infallible>((None,))
});
let routes = warp::get()
.and(warp::path("redirect"))
.and(query)
.and_then(handle_redirect);
// Get the oauth client and request a browser sign in
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
request.browser_authorization().open().unwrap();
warp::serve(routes).run(([127, 0, 0, 1], 8300)).await;
}
async fn handle_redirect(client_credential_option: Option<ClientCredentialsResponse>)
-> Result<Box<dyn warp::Reply>, warp::Rejection> {
match client_credential_option {
Some(client_credential_response) => {
// Print out for debugging purposes.
println!("{:#?}", client_credential_response);
// Request an access token.
request_access_token().await;
// Generic login page response.
Ok(Box::new(
"Successfully Logged In! You can close your browser.",
))
}
None => Err(warp::reject()),
}
}
async fn request_access_token() {
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
let access_token = request.access_token().send().await.unwrap();
println!("{:#?}", access_token);
oauth.access_token(access_token);
}
fn get_oauth_client() -> OAuth {
let mut oauth = OAuth::new();
oauth
.client_id(CLIENT_ID)
.client_secret(CLIENT_SECRET)
.tenant_id(TENANT_ID)
.add_scope("https://graph.microsoft.com/User.Invite.All")
.redirect_uri("http://localhost:8300/redirect")
// .authorize_url("https://login.microsoftonline.com/common/adminconsent")
.access_token_url("https://login.microsoftonline.com/common/oauth2/v2.0/token");
oauth
}
}
Note that i commented out the authorize url. If this url exits, a browser window opens, requesting an admin to log in. This must not happen. When it is commented out, it sends the request directly to <tenantId>/oauth2/v2.0/authorize instead of <tenantId>/oauth2/v2.0/adminconsent, which is what i want, it instead complains: AADSTS900144: The request body must contain the following parameter: 'scope'.. The scope is given though.
I already tried fiddling with urls for hours and also tried every other authentication concept in the crate, but it doesn't seem to work without interaction, which is not possible in my use case. Does someone know how to achieve that? (All permissions are granted as application permissions, not deligated)
Edit request: Please create and add the tag "graph-rs-sdk". I cannot create it myself but knowing the crate being used would be useful.

How to return contents as a file download in Axum?

I have a Rust application that is acting as a proxy. From the user perspective there is a web UI front end. This contains a button that when invoked will trigger a GET request to the Rust application. This in turn calls an external endpoint that returns the CSV file.
What I want is have the file download to the browser when the user clicks the button. Right now, the contents of the CSV file are returned to the browser rather than the file itself.
use std::net::SocketAddr;
use axum::{Router, Server};
use axum::extract::Json;
use axum::routing::get;
pub async fn downloadfile() -> Result<Json<String>, ApiError> {
let filename = ...;
let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
let path = format!("{}?filename={}", endpoint, filename);
let response = reqwest::get(path).await?;
let content = response.text().await?;
Ok(Json(content))
}
pub async fn serve(listen_addr: SocketAddr) {
let app = Router::new()
.route("/downloadfile", get(downloadfile));
Server::bind(&listen_addr)
.serve(app.into_make_service())
.await
.unwrap();
}
I understand the reason I'm getting the contents is because I'm returning the content string as JSON. This makes sense. However, what would I need to change to return the file itself so the browser downloads it directly for the user?
I've managed to resolve it and now returns the CSV as a file. Here's the function that works:
use axum::response::Headers;
use http::header::{self, HeaderName};
pub async fn downloadfile() -> Result<(Headers<[(HeaderName, &'static str); 2]>, String), ApiError> {
let filename = ...;
let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
let path = format!("{}?filename={}", endpoint, filename);
let response = reqwest::get(path).await?;
let content = response.text().await?;
let headers = Headers([
(header::CONTENT_TYPE, "text/csv; charset=utf-8"),
(header::CONTENT_DISPOSITION, "attachment; filename=\"data.csv\""),
]);
Ok((headers, content))
}
I'm still struggling with being able to set a dynamic file name, but that for another question.

Could not parse the ephemeral key response following protocol

I am trying to create an ephemeral key in my IOS app. I can successfully create a stripe customer that saves in my firebase console and on my stripe dashboard. However, when I try to create the ephemeral key, I am receiving the error in my ios console after trying to view the checkout controller.
'Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app.
and on my firebase function logs I am seeing,
createEphemeralKey
Request has incorrect Content-Type.
createEphemeralKey
Invalid request, unable to process.
in my index.js file, the code that I am using is
exports.createEphemeralKey = functions.https.onCall(async(data, context) => {
var stripeVersion = data.api_version;
const customerId = data.customer_id;
return stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: stripeVersion}
).then((key) => {
return key
}).catch((err) => {
console.log(err)
})
})
Below is how I create my stripe customer.
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${user.uid}/customer_id`).set(customer.id);
});
});
and then myAPIClient looks like.
enum APIError: Error {
case unknown
var localizedDescription: String {
switch self {
case .unknown:
return "Unknown error"
}
}
}
static let sharedClient = MyAPIClient()
var baseURLString: String? = "https://myProject.cloudfunctions.net/"
var baseURL: URL {
if let urlString = self.baseURLString, let url = URL(string: urlString) {
return url
} else {
fatalError()
}
}
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
Alamofire.request(url, method: .post, parameters: [
"api_version": apiVersion,
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
completion(nil, error)
}
}
}
On my checkOutVC, I have
var stripePublishableKey = "pk_test_testProjectKey"
var backendBaseURL: String? = "https://myProject.cloudfunctions.net"
let customerContext = STPCustomerContext(keyProvider: MyAPIClient())
init(price: Int, settings: Settings) {
if let stripePublishableKey = UserDefaults.standard.string(forKey: "StripePublishableKey") {
self.stripePublishableKey = stripePublishableKey
}
if let backendBaseURL = UserDefaults.standard.string(forKey: "StripeBackendBaseURL") {
self.backendBaseURL = backendBaseURL
}
let stripePublishableKey = self.stripePublishableKey
let backendBaseURL = self.backendBaseURL
assert(stripePublishableKey.hasPrefix("pk_"), "You must set your Stripe publishable key at the top of acceptWorker.swift to run this app.")
assert(backendBaseURL != nil, "You must set your backend base url at the top of acceptWorker.swift to run this app.")
Stripe.setDefaultPublishableKey(self.stripePublishableKey)
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = self.appleMerchantID
config.companyName = self.companyName
config.requiredBillingAddressFields = settings.requiredBillingAddressFields
config.requiredShippingAddressFields = settings.requiredShippingAddressFields
config.shippingType = settings.shippingType
config.additionalPaymentOptions = settings.additionalPaymentOptions
config.cardScanningEnabled = true
self.country = settings.country
self.paymentCurrency = settings.currency
self.theme = settings.theme
MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
let paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: settings.theme)
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I apologize for the long lines of code but I am really running into a brick wall. Why isnt the backend creating the ephemeralKey for customers?
Two things are jumping out at me:
You’ve written a callable type function (using onCall) but you’re
trying to call it with a normal HTTP request. These functions need to
be called with Firebase’s client library
(https://firebase.google.com/docs/functions/callable#call_the_function).
This stack overflow answer provides some great links about this:
Firebase Cloud Function to delete user.
Your firebase function is parsing stripe_version and customer_id from
data, but your request is only sending api_version. Where in your
code are you sending stripe_version and customer_id?

How to generate iOS Application Certificate for Fairplay implementation

We are developing a iOS music app. For content protection we are going to use Apples fairplay DRM system. I am following apple's HDLCatalog example for reference. While implementing i noticed there are two methods in AssetLoaderDelegate class that need to be implemented. I will appreciate if any one can help me out how to implemented below two methods. Thanks in advance.
1.)
public func fetchApplicationCertificate() -> Data? {
// MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
let applicationCertificate: Data? = nil
if applicationCertificate == nil {
fatalError("No certificate being returned by \(#function)!")
}
return applicationCertificate
}
2.)
public func contentKeyFromKeyServerModuleWithSPCData(spcData: Data, assetIDString: String) -> Data? {
// MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
let ckcData: Data? = nil
if ckcData == nil {
fatalError("No CKC being returned by \(#function)!")
}
return ckcData
}
I am updating here that we managed to implement fetchApplicationCertificate() method. Now we are facing problems for generating ckc data
Application Certificate
The application certificate is the DER formated public certificate created when you registered for Fairplay with Apple. This should be placed on a web server (AWS S3 is ideal) and retrieved once per application session.
CKC Data
This is specific to whomever you're using for Fairplay License Services. They will have specified an interface for sending the SPC data from your client to their license server. This could be JSON over REST, SOAP, MQ or anything they chose. You will have to ask them for the API spec.
Step 1 :
let queue = DispatchQueue(label: "fairplay.resourcerequests", attributes: [])
let url = URL(string: videoUrl)! // Streaming the video from this URL
let videoAsset = AVURLAsset(url: url, options: nil)
videoAsset.resourceLoader.setDelegate(self, queue: queue)
Step 2:
extension PlayerViewController : AVAssetResourceLoaderDelegate{
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
let url = loadingRequest.request.url
var error: NSError?
print("Player Delegate Method")
print("URL Schema Is \(url?.scheme! ?? "")")
if((url?.scheme ?? "") == "skd"),
let assetString = url!.host, let assetID = assetString.data(using: String.Encoding.utf8) // Get the URI for the content key.{guard let fetchedCertificate = self.fetchedCertificate else { return false} // Get the application certificate from the server.
let requestedBytes = try loadingRequest.streamingContentKeyRequestData(forApp: fetchedCertificate, contentIdentifier: assetID, options: nil)
do{
print("Online Video Streaming Going On")
let responseData = try contentKeyFromKeyServerModuleWithRequestData(requestedBytes, assetString: assetString, expiryDuration: expiryDuration)
guard let dataRequest = loadingRequest.dataRequest else {
// print("Failed to get instance of AVAssetResourceLoadingDataRequest (loadingRequest.dataRequest).")
return false
}
dataRequest.respond(with: responseData)
if let infoRequest = loadingRequest.contentInformationRequest,
expiryDuration != 0.0
{
infoRequest.renewalDate = Date(timeIntervalSinceNow: expiryDuration)
infoRequest.contentType = "application/octet-stream"
infoRequest.contentLength = Int64(responseData.count)
infoRequest.isByteRangeAccessSupported = false
}
// Treat the processing of the requested resource as complete.
loadingRequest.finishLoading()
// The resource request has been handled regardless of whether the server returned an error.
return true
}catch let e as NSError
{
error = e
// print("content key error\(error)")
}
}catch let e as NSError {
error = e
// Resource loading failed with an error.
// print("streamingContentKeyRequestDataForApp failure: \(error.localizedDescription)")
}}}
Step 3:
func contentKeyFromKeyServerModuleWithRequestData(_ requestBytes: Data, assetString: String, expiryDuration: TimeInterval?=0.0, persitent:Bool?=true) throws -> Data {
// If the key server provided a CKC, return it.
// if let ckcData = ckcData {
// return ckcData
// }
// else
// {
let base64Decoded = requestBytes.base64EncodedString(options: NSData.Base64EncodingOptions())
//MARK: Get Fairplay license for the current user
NSLog("using ticket: %#", streamTicket )
if let returnData:Data = mediaMakerDRMLicenseCall(base64Decoded, ticket: streamTicket)
{
if returnData.count <= 0
{
// Otherwise, the CKC was not provided by key server. Fail with bogus error.
// Generate an error describing the failure.
throw NSError(domain: "com.example.apple-samplecode", code: 0, userInfo: [
NSLocalizedDescriptionKey: NSLocalizedString("Item cannot be played.", comment: "Item cannot be played."),
NSLocalizedFailureReasonErrorKey: NSLocalizedString("Could not get the content key from the Key Server.", comment: "Failure to successfully send the SPC to the Key Server and get the content key from the returned Content Key Context (CKC) message.")
])
}
else
{
return returnData
}
}
//}
}
Step 4:
func mediaMakerDRMLicenseCall(_ playerSPC : String, ticket : String) -> Data{// API Call to fetch license from client server}

Swift WKWebView

how to handle NTLM authentication using WKWebView, the aim is to load a secured URL with credentials (Sharepoint Hosted application) similarly I wanted to add a key value pair to the http request
any code sample using swift2.0 that handles my case will be appreciated .
I went through the documentation and I have found the solutions below is the equivalent of the method shouldSatratLoading etc..
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print(" decidePolicyForNavigationAction.......")
let headerArr = navigationAction.request.allHTTPHeaderFields?.keys.array
let headerIsPresent = headerArr?.contains(APP_HEADER_ID)
if headerIsPresent! {
decisionHandler(.Allow)
}else{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
dispatch_async(dispatch_get_main_queue(), {
let newRequest: NSMutableURLRequest = navigationAction.request as! NSMutableURLRequest
// set new header
newRequest.addValue(APP_HEADER_VALUE, forHTTPHeaderField:APP_HEADER_ID)
// reload the request
webView.loadRequest(newRequest)
})
})
decisionHandler(.Cancel)
}
}
If anyone needs more info please let me know

Resources